| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | 1 | var mongoose = require('mongoose'), |
| 3 | connectionString; | |
| 4 | ||
| 5 | 1 | module.exports.config = function (nconf) { |
| 6 | 1 | console.log('***'); |
| 7 | // 27017 is mandatory to socket.io-mongo to avoid localhost:Nan error on startup | |
| 8 | 1 | connectionString = 'mongodb://localhost:27017/innovatio'; |
| 9 | 1 | mongoose.connect(connectionString); |
| 10 | 1 | var db = mongoose.connection; |
| 11 | ||
| 12 | // set dynamix databse connection to store conf for resuse in connect-mongo session store | |
| 13 | 1 | nconf.set('middleware:session:config:mongoose_connection',db); |
| 14 | ||
| 15 | ||
| 16 | 1 | db.on('error', console.error.bind(console, 'connection error:')); |
| 17 | 1 | db.once('open', function callback() { |
| 18 | 0 | console.log('db connection open'); |
| 19 | }); | |
| 20 | }; | |
| 21 | ||
| 22 | 1 | module.exports.connectionString = function(){ |
| 23 | 1 | return connectionString; |
| 24 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | /*global setImmediate: false, setTimeout: false, console: false */ | |
| 2 | 1 | (function () { |
| 3 | ||
| 4 | 1 | var async = {}; |
| 5 | ||
| 6 | // global on the server, window in the browser | |
| 7 | 1 | var root, previous_async; |
| 8 | ||
| 9 | 1 | root = this; |
| 10 | 1 | if (root != null) { |
| 11 | 1 | previous_async = root.async; |
| 12 | } | |
| 13 | ||
| 14 | 1 | async.noConflict = function () { |
| 15 | 0 | root.async = previous_async; |
| 16 | 0 | return async; |
| 17 | }; | |
| 18 | ||
| 19 | 1 | function only_once(fn) { |
| 20 | 0 | var called = false; |
| 21 | 0 | return function() { |
| 22 | 0 | if (called) throw new Error("Callback was already called."); |
| 23 | 0 | called = true; |
| 24 | 0 | fn.apply(root, arguments); |
| 25 | } | |
| 26 | } | |
| 27 | ||
| 28 | //// cross-browser compatiblity functions //// | |
| 29 | ||
| 30 | 1 | var _each = function (arr, iterator) { |
| 31 | 0 | if (arr.forEach) { |
| 32 | 0 | return arr.forEach(iterator); |
| 33 | } | |
| 34 | 0 | for (var i = 0; i < arr.length; i += 1) { |
| 35 | 0 | iterator(arr[i], i, arr); |
| 36 | } | |
| 37 | }; | |
| 38 | ||
| 39 | 1 | var _map = function (arr, iterator) { |
| 40 | 0 | if (arr.map) { |
| 41 | 0 | return arr.map(iterator); |
| 42 | } | |
| 43 | 0 | var results = []; |
| 44 | 0 | _each(arr, function (x, i, a) { |
| 45 | 0 | results.push(iterator(x, i, a)); |
| 46 | }); | |
| 47 | 0 | return results; |
| 48 | }; | |
| 49 | ||
| 50 | 1 | var _reduce = function (arr, iterator, memo) { |
| 51 | 0 | if (arr.reduce) { |
| 52 | 0 | return arr.reduce(iterator, memo); |
| 53 | } | |
| 54 | 0 | _each(arr, function (x, i, a) { |
| 55 | 0 | memo = iterator(memo, x, i, a); |
| 56 | }); | |
| 57 | 0 | return memo; |
| 58 | }; | |
| 59 | ||
| 60 | 1 | var _keys = function (obj) { |
| 61 | 0 | if (Object.keys) { |
| 62 | 0 | return Object.keys(obj); |
| 63 | } | |
| 64 | 0 | var keys = []; |
| 65 | 0 | for (var k in obj) { |
| 66 | 0 | if (obj.hasOwnProperty(k)) { |
| 67 | 0 | keys.push(k); |
| 68 | } | |
| 69 | } | |
| 70 | 0 | return keys; |
| 71 | }; | |
| 72 | ||
| 73 | //// exported async module functions //// | |
| 74 | ||
| 75 | //// nextTick implementation with browser-compatible fallback //// | |
| 76 | 1 | if (typeof process === 'undefined' || !(process.nextTick)) { |
| 77 | 0 | if (typeof setImmediate === 'function') { |
| 78 | 0 | async.nextTick = function (fn) { |
| 79 | // not a direct alias for IE10 compatibility | |
| 80 | 0 | setImmediate(fn); |
| 81 | }; | |
| 82 | 0 | async.setImmediate = async.nextTick; |
| 83 | } | |
| 84 | else { | |
| 85 | 0 | async.nextTick = function (fn) { |
| 86 | 0 | setTimeout(fn, 0); |
| 87 | }; | |
| 88 | 0 | async.setImmediate = async.nextTick; |
| 89 | } | |
| 90 | } | |
| 91 | else { | |
| 92 | 1 | async.nextTick = process.nextTick; |
| 93 | 1 | if (typeof setImmediate !== 'undefined') { |
| 94 | 1 | async.setImmediate = function (fn) { |
| 95 | // not a direct alias for IE10 compatibility | |
| 96 | 0 | setImmediate(fn); |
| 97 | }; | |
| 98 | } | |
| 99 | else { | |
| 100 | 0 | async.setImmediate = async.nextTick; |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | 1 | async.each = function (arr, iterator, callback) { |
| 105 | 0 | callback = callback || function () {}; |
| 106 | 0 | if (!arr.length) { |
| 107 | 0 | return callback(); |
| 108 | } | |
| 109 | 0 | var completed = 0; |
| 110 | 0 | _each(arr, function (x) { |
| 111 | 0 | iterator(x, only_once(function (err) { |
| 112 | 0 | if (err) { |
| 113 | 0 | callback(err); |
| 114 | 0 | callback = function () {}; |
| 115 | } | |
| 116 | else { | |
| 117 | 0 | completed += 1; |
| 118 | 0 | if (completed >= arr.length) { |
| 119 | 0 | callback(null); |
| 120 | } | |
| 121 | } | |
| 122 | })); | |
| 123 | }); | |
| 124 | }; | |
| 125 | 1 | async.forEach = async.each; |
| 126 | ||
| 127 | 1 | async.eachSeries = function (arr, iterator, callback) { |
| 128 | 0 | callback = callback || function () {}; |
| 129 | 0 | if (!arr.length) { |
| 130 | 0 | return callback(); |
| 131 | } | |
| 132 | 0 | var completed = 0; |
| 133 | 0 | var iterate = function () { |
| 134 | 0 | iterator(arr[completed], function (err) { |
| 135 | 0 | if (err) { |
| 136 | 0 | callback(err); |
| 137 | 0 | callback = function () {}; |
| 138 | } | |
| 139 | else { | |
| 140 | 0 | completed += 1; |
| 141 | 0 | if (completed >= arr.length) { |
| 142 | 0 | callback(null); |
| 143 | } | |
| 144 | else { | |
| 145 | 0 | iterate(); |
| 146 | } | |
| 147 | } | |
| 148 | }); | |
| 149 | }; | |
| 150 | 0 | iterate(); |
| 151 | }; | |
| 152 | 1 | async.forEachSeries = async.eachSeries; |
| 153 | ||
| 154 | 1 | async.eachLimit = function (arr, limit, iterator, callback) { |
| 155 | 0 | var fn = _eachLimit(limit); |
| 156 | 0 | fn.apply(null, [arr, iterator, callback]); |
| 157 | }; | |
| 158 | 1 | async.forEachLimit = async.eachLimit; |
| 159 | ||
| 160 | 1 | var _eachLimit = function (limit) { |
| 161 | ||
| 162 | 0 | return function (arr, iterator, callback) { |
| 163 | 0 | callback = callback || function () {}; |
| 164 | 0 | if (!arr.length || limit <= 0) { |
| 165 | 0 | return callback(); |
| 166 | } | |
| 167 | 0 | var completed = 0; |
| 168 | 0 | var started = 0; |
| 169 | 0 | var running = 0; |
| 170 | ||
| 171 | 0 | (function replenish () { |
| 172 | 0 | if (completed >= arr.length) { |
| 173 | 0 | return callback(); |
| 174 | } | |
| 175 | ||
| 176 | 0 | while (running < limit && started < arr.length) { |
| 177 | 0 | started += 1; |
| 178 | 0 | running += 1; |
| 179 | 0 | iterator(arr[started - 1], function (err) { |
| 180 | 0 | if (err) { |
| 181 | 0 | callback(err); |
| 182 | 0 | callback = function () {}; |
| 183 | } | |
| 184 | else { | |
| 185 | 0 | completed += 1; |
| 186 | 0 | running -= 1; |
| 187 | 0 | if (completed >= arr.length) { |
| 188 | 0 | callback(); |
| 189 | } | |
| 190 | else { | |
| 191 | 0 | replenish(); |
| 192 | } | |
| 193 | } | |
| 194 | }); | |
| 195 | } | |
| 196 | })(); | |
| 197 | }; | |
| 198 | }; | |
| 199 | ||
| 200 | ||
| 201 | 1 | var doParallel = function (fn) { |
| 202 | 6 | return function () { |
| 203 | 0 | var args = Array.prototype.slice.call(arguments); |
| 204 | 0 | return fn.apply(null, [async.each].concat(args)); |
| 205 | }; | |
| 206 | }; | |
| 207 | 1 | var doParallelLimit = function(limit, fn) { |
| 208 | 0 | return function () { |
| 209 | 0 | var args = Array.prototype.slice.call(arguments); |
| 210 | 0 | return fn.apply(null, [_eachLimit(limit)].concat(args)); |
| 211 | }; | |
| 212 | }; | |
| 213 | 1 | var doSeries = function (fn) { |
| 214 | 6 | return function () { |
| 215 | 0 | var args = Array.prototype.slice.call(arguments); |
| 216 | 0 | return fn.apply(null, [async.eachSeries].concat(args)); |
| 217 | }; | |
| 218 | }; | |
| 219 | ||
| 220 | ||
| 221 | 1 | var _asyncMap = function (eachfn, arr, iterator, callback) { |
| 222 | 0 | var results = []; |
| 223 | 0 | arr = _map(arr, function (x, i) { |
| 224 | 0 | return {index: i, value: x}; |
| 225 | }); | |
| 226 | 0 | eachfn(arr, function (x, callback) { |
| 227 | 0 | iterator(x.value, function (err, v) { |
| 228 | 0 | results[x.index] = v; |
| 229 | 0 | callback(err); |
| 230 | }); | |
| 231 | }, function (err) { | |
| 232 | 0 | callback(err, results); |
| 233 | }); | |
| 234 | }; | |
| 235 | 1 | async.map = doParallel(_asyncMap); |
| 236 | 1 | async.mapSeries = doSeries(_asyncMap); |
| 237 | 1 | async.mapLimit = function (arr, limit, iterator, callback) { |
| 238 | 0 | return _mapLimit(limit)(arr, iterator, callback); |
| 239 | }; | |
| 240 | ||
| 241 | 1 | var _mapLimit = function(limit) { |
| 242 | 0 | return doParallelLimit(limit, _asyncMap); |
| 243 | }; | |
| 244 | ||
| 245 | // reduce only has a series version, as doing reduce in parallel won't | |
| 246 | // work in many situations. | |
| 247 | 1 | async.reduce = function (arr, memo, iterator, callback) { |
| 248 | 0 | async.eachSeries(arr, function (x, callback) { |
| 249 | 0 | iterator(memo, x, function (err, v) { |
| 250 | 0 | memo = v; |
| 251 | 0 | callback(err); |
| 252 | }); | |
| 253 | }, function (err) { | |
| 254 | 0 | callback(err, memo); |
| 255 | }); | |
| 256 | }; | |
| 257 | // inject alias | |
| 258 | 1 | async.inject = async.reduce; |
| 259 | // foldl alias | |
| 260 | 1 | async.foldl = async.reduce; |
| 261 | ||
| 262 | 1 | async.reduceRight = function (arr, memo, iterator, callback) { |
| 263 | 0 | var reversed = _map(arr, function (x) { |
| 264 | 0 | return x; |
| 265 | }).reverse(); | |
| 266 | 0 | async.reduce(reversed, memo, iterator, callback); |
| 267 | }; | |
| 268 | // foldr alias | |
| 269 | 1 | async.foldr = async.reduceRight; |
| 270 | ||
| 271 | 1 | var _filter = function (eachfn, arr, iterator, callback) { |
| 272 | 0 | var results = []; |
| 273 | 0 | arr = _map(arr, function (x, i) { |
| 274 | 0 | return {index: i, value: x}; |
| 275 | }); | |
| 276 | 0 | eachfn(arr, function (x, callback) { |
| 277 | 0 | iterator(x.value, function (v) { |
| 278 | 0 | if (v) { |
| 279 | 0 | results.push(x); |
| 280 | } | |
| 281 | 0 | callback(); |
| 282 | }); | |
| 283 | }, function (err) { | |
| 284 | 0 | callback(_map(results.sort(function (a, b) { |
| 285 | 0 | return a.index - b.index; |
| 286 | }), function (x) { | |
| 287 | 0 | return x.value; |
| 288 | })); | |
| 289 | }); | |
| 290 | }; | |
| 291 | 1 | async.filter = doParallel(_filter); |
| 292 | 1 | async.filterSeries = doSeries(_filter); |
| 293 | // select alias | |
| 294 | 1 | async.select = async.filter; |
| 295 | 1 | async.selectSeries = async.filterSeries; |
| 296 | ||
| 297 | 1 | var _reject = function (eachfn, arr, iterator, callback) { |
| 298 | 0 | var results = []; |
| 299 | 0 | arr = _map(arr, function (x, i) { |
| 300 | 0 | return {index: i, value: x}; |
| 301 | }); | |
| 302 | 0 | eachfn(arr, function (x, callback) { |
| 303 | 0 | iterator(x.value, function (v) { |
| 304 | 0 | if (!v) { |
| 305 | 0 | results.push(x); |
| 306 | } | |
| 307 | 0 | callback(); |
| 308 | }); | |
| 309 | }, function (err) { | |
| 310 | 0 | callback(_map(results.sort(function (a, b) { |
| 311 | 0 | return a.index - b.index; |
| 312 | }), function (x) { | |
| 313 | 0 | return x.value; |
| 314 | })); | |
| 315 | }); | |
| 316 | }; | |
| 317 | 1 | async.reject = doParallel(_reject); |
| 318 | 1 | async.rejectSeries = doSeries(_reject); |
| 319 | ||
| 320 | 1 | var _detect = function (eachfn, arr, iterator, main_callback) { |
| 321 | 0 | eachfn(arr, function (x, callback) { |
| 322 | 0 | iterator(x, function (result) { |
| 323 | 0 | if (result) { |
| 324 | 0 | main_callback(x); |
| 325 | 0 | main_callback = function () {}; |
| 326 | } | |
| 327 | else { | |
| 328 | 0 | callback(); |
| 329 | } | |
| 330 | }); | |
| 331 | }, function (err) { | |
| 332 | 0 | main_callback(); |
| 333 | }); | |
| 334 | }; | |
| 335 | 1 | async.detect = doParallel(_detect); |
| 336 | 1 | async.detectSeries = doSeries(_detect); |
| 337 | ||
| 338 | 1 | async.some = function (arr, iterator, main_callback) { |
| 339 | 0 | async.each(arr, function (x, callback) { |
| 340 | 0 | iterator(x, function (v) { |
| 341 | 0 | if (v) { |
| 342 | 0 | main_callback(true); |
| 343 | 0 | main_callback = function () {}; |
| 344 | } | |
| 345 | 0 | callback(); |
| 346 | }); | |
| 347 | }, function (err) { | |
| 348 | 0 | main_callback(false); |
| 349 | }); | |
| 350 | }; | |
| 351 | // any alias | |
| 352 | 1 | async.any = async.some; |
| 353 | ||
| 354 | 1 | async.every = function (arr, iterator, main_callback) { |
| 355 | 0 | async.each(arr, function (x, callback) { |
| 356 | 0 | iterator(x, function (v) { |
| 357 | 0 | if (!v) { |
| 358 | 0 | main_callback(false); |
| 359 | 0 | main_callback = function () {}; |
| 360 | } | |
| 361 | 0 | callback(); |
| 362 | }); | |
| 363 | }, function (err) { | |
| 364 | 0 | main_callback(true); |
| 365 | }); | |
| 366 | }; | |
| 367 | // all alias | |
| 368 | 1 | async.all = async.every; |
| 369 | ||
| 370 | 1 | async.sortBy = function (arr, iterator, callback) { |
| 371 | 0 | async.map(arr, function (x, callback) { |
| 372 | 0 | iterator(x, function (err, criteria) { |
| 373 | 0 | if (err) { |
| 374 | 0 | callback(err); |
| 375 | } | |
| 376 | else { | |
| 377 | 0 | callback(null, {value: x, criteria: criteria}); |
| 378 | } | |
| 379 | }); | |
| 380 | }, function (err, results) { | |
| 381 | 0 | if (err) { |
| 382 | 0 | return callback(err); |
| 383 | } | |
| 384 | else { | |
| 385 | 0 | var fn = function (left, right) { |
| 386 | 0 | var a = left.criteria, b = right.criteria; |
| 387 | 0 | return a < b ? -1 : a > b ? 1 : 0; |
| 388 | }; | |
| 389 | 0 | callback(null, _map(results.sort(fn), function (x) { |
| 390 | 0 | return x.value; |
| 391 | })); | |
| 392 | } | |
| 393 | }); | |
| 394 | }; | |
| 395 | ||
| 396 | 1 | async.auto = function (tasks, callback) { |
| 397 | 0 | callback = callback || function () {}; |
| 398 | 0 | var keys = _keys(tasks); |
| 399 | 0 | if (!keys.length) { |
| 400 | 0 | return callback(null); |
| 401 | } | |
| 402 | ||
| 403 | 0 | var results = {}; |
| 404 | ||
| 405 | 0 | var listeners = []; |
| 406 | 0 | var addListener = function (fn) { |
| 407 | 0 | listeners.unshift(fn); |
| 408 | }; | |
| 409 | 0 | var removeListener = function (fn) { |
| 410 | 0 | for (var i = 0; i < listeners.length; i += 1) { |
| 411 | 0 | if (listeners[i] === fn) { |
| 412 | 0 | listeners.splice(i, 1); |
| 413 | 0 | return; |
| 414 | } | |
| 415 | } | |
| 416 | }; | |
| 417 | 0 | var taskComplete = function () { |
| 418 | 0 | _each(listeners.slice(0), function (fn) { |
| 419 | 0 | fn(); |
| 420 | }); | |
| 421 | }; | |
| 422 | ||
| 423 | 0 | addListener(function () { |
| 424 | 0 | if (_keys(results).length === keys.length) { |
| 425 | 0 | callback(null, results); |
| 426 | 0 | callback = function () {}; |
| 427 | } | |
| 428 | }); | |
| 429 | ||
| 430 | 0 | _each(keys, function (k) { |
| 431 | 0 | var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; |
| 432 | 0 | var taskCallback = function (err) { |
| 433 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 434 | 0 | if (args.length <= 1) { |
| 435 | 0 | args = args[0]; |
| 436 | } | |
| 437 | 0 | if (err) { |
| 438 | 0 | var safeResults = {}; |
| 439 | 0 | _each(_keys(results), function(rkey) { |
| 440 | 0 | safeResults[rkey] = results[rkey]; |
| 441 | }); | |
| 442 | 0 | safeResults[k] = args; |
| 443 | 0 | callback(err, safeResults); |
| 444 | // stop subsequent errors hitting callback multiple times | |
| 445 | 0 | callback = function () {}; |
| 446 | } | |
| 447 | else { | |
| 448 | 0 | results[k] = args; |
| 449 | 0 | async.setImmediate(taskComplete); |
| 450 | } | |
| 451 | }; | |
| 452 | 0 | var requires = task.slice(0, Math.abs(task.length - 1)) || []; |
| 453 | 0 | var ready = function () { |
| 454 | 0 | return _reduce(requires, function (a, x) { |
| 455 | 0 | return (a && results.hasOwnProperty(x)); |
| 456 | }, true) && !results.hasOwnProperty(k); | |
| 457 | }; | |
| 458 | 0 | if (ready()) { |
| 459 | 0 | task[task.length - 1](taskCallback, results); |
| 460 | } | |
| 461 | else { | |
| 462 | 0 | var listener = function () { |
| 463 | 0 | if (ready()) { |
| 464 | 0 | removeListener(listener); |
| 465 | 0 | task[task.length - 1](taskCallback, results); |
| 466 | } | |
| 467 | }; | |
| 468 | 0 | addListener(listener); |
| 469 | } | |
| 470 | }); | |
| 471 | }; | |
| 472 | ||
| 473 | 1 | async.waterfall = function (tasks, callback) { |
| 474 | 0 | callback = callback || function () {}; |
| 475 | 0 | if (tasks.constructor !== Array) { |
| 476 | 0 | var err = new Error('First argument to waterfall must be an array of functions'); |
| 477 | 0 | return callback(err); |
| 478 | } | |
| 479 | 0 | if (!tasks.length) { |
| 480 | 0 | return callback(); |
| 481 | } | |
| 482 | 0 | var wrapIterator = function (iterator) { |
| 483 | 0 | return function (err) { |
| 484 | 0 | if (err) { |
| 485 | 0 | callback.apply(null, arguments); |
| 486 | 0 | callback = function () {}; |
| 487 | } | |
| 488 | else { | |
| 489 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 490 | 0 | var next = iterator.next(); |
| 491 | 0 | if (next) { |
| 492 | 0 | args.push(wrapIterator(next)); |
| 493 | } | |
| 494 | else { | |
| 495 | 0 | args.push(callback); |
| 496 | } | |
| 497 | 0 | async.setImmediate(function () { |
| 498 | 0 | iterator.apply(null, args); |
| 499 | }); | |
| 500 | } | |
| 501 | }; | |
| 502 | }; | |
| 503 | 0 | wrapIterator(async.iterator(tasks))(); |
| 504 | }; | |
| 505 | ||
| 506 | 1 | var _parallel = function(eachfn, tasks, callback) { |
| 507 | 0 | callback = callback || function () {}; |
| 508 | 0 | if (tasks.constructor === Array) { |
| 509 | 0 | eachfn.map(tasks, function (fn, callback) { |
| 510 | 0 | if (fn) { |
| 511 | 0 | fn(function (err) { |
| 512 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 513 | 0 | if (args.length <= 1) { |
| 514 | 0 | args = args[0]; |
| 515 | } | |
| 516 | 0 | callback.call(null, err, args); |
| 517 | }); | |
| 518 | } | |
| 519 | }, callback); | |
| 520 | } | |
| 521 | else { | |
| 522 | 0 | var results = {}; |
| 523 | 0 | eachfn.each(_keys(tasks), function (k, callback) { |
| 524 | 0 | tasks[k](function (err) { |
| 525 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 526 | 0 | if (args.length <= 1) { |
| 527 | 0 | args = args[0]; |
| 528 | } | |
| 529 | 0 | results[k] = args; |
| 530 | 0 | callback(err); |
| 531 | }); | |
| 532 | }, function (err) { | |
| 533 | 0 | callback(err, results); |
| 534 | }); | |
| 535 | } | |
| 536 | }; | |
| 537 | ||
| 538 | 1 | async.parallel = function (tasks, callback) { |
| 539 | 0 | _parallel({ map: async.map, each: async.each }, tasks, callback); |
| 540 | }; | |
| 541 | ||
| 542 | 1 | async.parallelLimit = function(tasks, limit, callback) { |
| 543 | 0 | _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); |
| 544 | }; | |
| 545 | ||
| 546 | 1 | async.series = function (tasks, callback) { |
| 547 | 0 | callback = callback || function () {}; |
| 548 | 0 | if (tasks.constructor === Array) { |
| 549 | 0 | async.mapSeries(tasks, function (fn, callback) { |
| 550 | 0 | if (fn) { |
| 551 | 0 | fn(function (err) { |
| 552 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 553 | 0 | if (args.length <= 1) { |
| 554 | 0 | args = args[0]; |
| 555 | } | |
| 556 | 0 | callback.call(null, err, args); |
| 557 | }); | |
| 558 | } | |
| 559 | }, callback); | |
| 560 | } | |
| 561 | else { | |
| 562 | 0 | var results = {}; |
| 563 | 0 | async.eachSeries(_keys(tasks), function (k, callback) { |
| 564 | 0 | tasks[k](function (err) { |
| 565 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 566 | 0 | if (args.length <= 1) { |
| 567 | 0 | args = args[0]; |
| 568 | } | |
| 569 | 0 | results[k] = args; |
| 570 | 0 | callback(err); |
| 571 | }); | |
| 572 | }, function (err) { | |
| 573 | 0 | callback(err, results); |
| 574 | }); | |
| 575 | } | |
| 576 | }; | |
| 577 | ||
| 578 | 1 | async.iterator = function (tasks) { |
| 579 | 0 | var makeCallback = function (index) { |
| 580 | 0 | var fn = function () { |
| 581 | 0 | if (tasks.length) { |
| 582 | 0 | tasks[index].apply(null, arguments); |
| 583 | } | |
| 584 | 0 | return fn.next(); |
| 585 | }; | |
| 586 | 0 | fn.next = function () { |
| 587 | 0 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; |
| 588 | }; | |
| 589 | 0 | return fn; |
| 590 | }; | |
| 591 | 0 | return makeCallback(0); |
| 592 | }; | |
| 593 | ||
| 594 | 1 | async.apply = function (fn) { |
| 595 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 596 | 0 | return function () { |
| 597 | 0 | return fn.apply( |
| 598 | null, args.concat(Array.prototype.slice.call(arguments)) | |
| 599 | ); | |
| 600 | }; | |
| 601 | }; | |
| 602 | ||
| 603 | 1 | var _concat = function (eachfn, arr, fn, callback) { |
| 604 | 0 | var r = []; |
| 605 | 0 | eachfn(arr, function (x, cb) { |
| 606 | 0 | fn(x, function (err, y) { |
| 607 | 0 | r = r.concat(y || []); |
| 608 | 0 | cb(err); |
| 609 | }); | |
| 610 | }, function (err) { | |
| 611 | 0 | callback(err, r); |
| 612 | }); | |
| 613 | }; | |
| 614 | 1 | async.concat = doParallel(_concat); |
| 615 | 1 | async.concatSeries = doSeries(_concat); |
| 616 | ||
| 617 | 1 | async.whilst = function (test, iterator, callback) { |
| 618 | 0 | if (test()) { |
| 619 | 0 | iterator(function (err) { |
| 620 | 0 | if (err) { |
| 621 | 0 | return callback(err); |
| 622 | } | |
| 623 | 0 | async.whilst(test, iterator, callback); |
| 624 | }); | |
| 625 | } | |
| 626 | else { | |
| 627 | 0 | callback(); |
| 628 | } | |
| 629 | }; | |
| 630 | ||
| 631 | 1 | async.doWhilst = function (iterator, test, callback) { |
| 632 | 0 | iterator(function (err) { |
| 633 | 0 | if (err) { |
| 634 | 0 | return callback(err); |
| 635 | } | |
| 636 | 0 | if (test()) { |
| 637 | 0 | async.doWhilst(iterator, test, callback); |
| 638 | } | |
| 639 | else { | |
| 640 | 0 | callback(); |
| 641 | } | |
| 642 | }); | |
| 643 | }; | |
| 644 | ||
| 645 | 1 | async.until = function (test, iterator, callback) { |
| 646 | 0 | if (!test()) { |
| 647 | 0 | iterator(function (err) { |
| 648 | 0 | if (err) { |
| 649 | 0 | return callback(err); |
| 650 | } | |
| 651 | 0 | async.until(test, iterator, callback); |
| 652 | }); | |
| 653 | } | |
| 654 | else { | |
| 655 | 0 | callback(); |
| 656 | } | |
| 657 | }; | |
| 658 | ||
| 659 | 1 | async.doUntil = function (iterator, test, callback) { |
| 660 | 0 | iterator(function (err) { |
| 661 | 0 | if (err) { |
| 662 | 0 | return callback(err); |
| 663 | } | |
| 664 | 0 | if (!test()) { |
| 665 | 0 | async.doUntil(iterator, test, callback); |
| 666 | } | |
| 667 | else { | |
| 668 | 0 | callback(); |
| 669 | } | |
| 670 | }); | |
| 671 | }; | |
| 672 | ||
| 673 | 1 | async.queue = function (worker, concurrency) { |
| 674 | 0 | if (concurrency === undefined) { |
| 675 | 0 | concurrency = 1; |
| 676 | } | |
| 677 | 0 | function _insert(q, data, pos, callback) { |
| 678 | 0 | if(data.constructor !== Array) { |
| 679 | 0 | data = [data]; |
| 680 | } | |
| 681 | 0 | _each(data, function(task) { |
| 682 | 0 | var item = { |
| 683 | data: task, | |
| 684 | callback: typeof callback === 'function' ? callback : null | |
| 685 | }; | |
| 686 | ||
| 687 | 0 | if (pos) { |
| 688 | 0 | q.tasks.unshift(item); |
| 689 | } else { | |
| 690 | 0 | q.tasks.push(item); |
| 691 | } | |
| 692 | ||
| 693 | 0 | if (q.saturated && q.tasks.length === concurrency) { |
| 694 | 0 | q.saturated(); |
| 695 | } | |
| 696 | 0 | async.setImmediate(q.process); |
| 697 | }); | |
| 698 | } | |
| 699 | ||
| 700 | 0 | var workers = 0; |
| 701 | 0 | var q = { |
| 702 | tasks: [], | |
| 703 | concurrency: concurrency, | |
| 704 | saturated: null, | |
| 705 | empty: null, | |
| 706 | drain: null, | |
| 707 | push: function (data, callback) { | |
| 708 | 0 | _insert(q, data, false, callback); |
| 709 | }, | |
| 710 | unshift: function (data, callback) { | |
| 711 | 0 | _insert(q, data, true, callback); |
| 712 | }, | |
| 713 | process: function () { | |
| 714 | 0 | if (workers < q.concurrency && q.tasks.length) { |
| 715 | 0 | var task = q.tasks.shift(); |
| 716 | 0 | if (q.empty && q.tasks.length === 0) { |
| 717 | 0 | q.empty(); |
| 718 | } | |
| 719 | 0 | workers += 1; |
| 720 | 0 | var next = function () { |
| 721 | 0 | workers -= 1; |
| 722 | 0 | if (task.callback) { |
| 723 | 0 | task.callback.apply(task, arguments); |
| 724 | } | |
| 725 | 0 | if (q.drain && q.tasks.length + workers === 0) { |
| 726 | 0 | q.drain(); |
| 727 | } | |
| 728 | 0 | q.process(); |
| 729 | }; | |
| 730 | 0 | var cb = only_once(next); |
| 731 | 0 | worker(task.data, cb); |
| 732 | } | |
| 733 | }, | |
| 734 | length: function () { | |
| 735 | 0 | return q.tasks.length; |
| 736 | }, | |
| 737 | running: function () { | |
| 738 | 0 | return workers; |
| 739 | } | |
| 740 | }; | |
| 741 | 0 | return q; |
| 742 | }; | |
| 743 | ||
| 744 | 1 | async.cargo = function (worker, payload) { |
| 745 | 0 | var working = false, |
| 746 | tasks = []; | |
| 747 | ||
| 748 | 0 | var cargo = { |
| 749 | tasks: tasks, | |
| 750 | payload: payload, | |
| 751 | saturated: null, | |
| 752 | empty: null, | |
| 753 | drain: null, | |
| 754 | push: function (data, callback) { | |
| 755 | 0 | if(data.constructor !== Array) { |
| 756 | 0 | data = [data]; |
| 757 | } | |
| 758 | 0 | _each(data, function(task) { |
| 759 | 0 | tasks.push({ |
| 760 | data: task, | |
| 761 | callback: typeof callback === 'function' ? callback : null | |
| 762 | }); | |
| 763 | 0 | if (cargo.saturated && tasks.length === payload) { |
| 764 | 0 | cargo.saturated(); |
| 765 | } | |
| 766 | }); | |
| 767 | 0 | async.setImmediate(cargo.process); |
| 768 | }, | |
| 769 | process: function process() { | |
| 770 | 0 | if (working) return; |
| 771 | 0 | if (tasks.length === 0) { |
| 772 | 0 | if(cargo.drain) cargo.drain(); |
| 773 | 0 | return; |
| 774 | } | |
| 775 | ||
| 776 | 0 | var ts = typeof payload === 'number' |
| 777 | ? tasks.splice(0, payload) | |
| 778 | : tasks.splice(0); | |
| 779 | ||
| 780 | 0 | var ds = _map(ts, function (task) { |
| 781 | 0 | return task.data; |
| 782 | }); | |
| 783 | ||
| 784 | 0 | if(cargo.empty) cargo.empty(); |
| 785 | 0 | working = true; |
| 786 | 0 | worker(ds, function () { |
| 787 | 0 | working = false; |
| 788 | ||
| 789 | 0 | var args = arguments; |
| 790 | 0 | _each(ts, function (data) { |
| 791 | 0 | if (data.callback) { |
| 792 | 0 | data.callback.apply(null, args); |
| 793 | } | |
| 794 | }); | |
| 795 | ||
| 796 | 0 | process(); |
| 797 | }); | |
| 798 | }, | |
| 799 | length: function () { | |
| 800 | 0 | return tasks.length; |
| 801 | }, | |
| 802 | running: function () { | |
| 803 | 0 | return working; |
| 804 | } | |
| 805 | }; | |
| 806 | 0 | return cargo; |
| 807 | }; | |
| 808 | ||
| 809 | 1 | var _console_fn = function (name) { |
| 810 | 2 | return function (fn) { |
| 811 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 812 | 0 | fn.apply(null, args.concat([function (err) { |
| 813 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 814 | 0 | if (typeof console !== 'undefined') { |
| 815 | 0 | if (err) { |
| 816 | 0 | if (console.error) { |
| 817 | 0 | console.error(err); |
| 818 | } | |
| 819 | } | |
| 820 | 0 | else if (console[name]) { |
| 821 | 0 | _each(args, function (x) { |
| 822 | 0 | console[name](x); |
| 823 | }); | |
| 824 | } | |
| 825 | } | |
| 826 | }])); | |
| 827 | }; | |
| 828 | }; | |
| 829 | 1 | async.log = _console_fn('log'); |
| 830 | 1 | async.dir = _console_fn('dir'); |
| 831 | /*async.info = _console_fn('info'); | |
| 832 | async.warn = _console_fn('warn'); | |
| 833 | async.error = _console_fn('error');*/ | |
| 834 | ||
| 835 | 1 | async.memoize = function (fn, hasher) { |
| 836 | 0 | var memo = {}; |
| 837 | 0 | var queues = {}; |
| 838 | 0 | hasher = hasher || function (x) { |
| 839 | 0 | return x; |
| 840 | }; | |
| 841 | 0 | var memoized = function () { |
| 842 | 0 | var args = Array.prototype.slice.call(arguments); |
| 843 | 0 | var callback = args.pop(); |
| 844 | 0 | var key = hasher.apply(null, args); |
| 845 | 0 | if (key in memo) { |
| 846 | 0 | callback.apply(null, memo[key]); |
| 847 | } | |
| 848 | 0 | else if (key in queues) { |
| 849 | 0 | queues[key].push(callback); |
| 850 | } | |
| 851 | else { | |
| 852 | 0 | queues[key] = [callback]; |
| 853 | 0 | fn.apply(null, args.concat([function () { |
| 854 | 0 | memo[key] = arguments; |
| 855 | 0 | var q = queues[key]; |
| 856 | 0 | delete queues[key]; |
| 857 | 0 | for (var i = 0, l = q.length; i < l; i++) { |
| 858 | 0 | q[i].apply(null, arguments); |
| 859 | } | |
| 860 | }])); | |
| 861 | } | |
| 862 | }; | |
| 863 | 0 | memoized.memo = memo; |
| 864 | 0 | memoized.unmemoized = fn; |
| 865 | 0 | return memoized; |
| 866 | }; | |
| 867 | ||
| 868 | 1 | async.unmemoize = function (fn) { |
| 869 | 0 | return function () { |
| 870 | 0 | return (fn.unmemoized || fn).apply(null, arguments); |
| 871 | }; | |
| 872 | }; | |
| 873 | ||
| 874 | 1 | async.times = function (count, iterator, callback) { |
| 875 | 0 | var counter = []; |
| 876 | 0 | for (var i = 0; i < count; i++) { |
| 877 | 0 | counter.push(i); |
| 878 | } | |
| 879 | 0 | return async.map(counter, iterator, callback); |
| 880 | }; | |
| 881 | ||
| 882 | 1 | async.timesSeries = function (count, iterator, callback) { |
| 883 | 0 | var counter = []; |
| 884 | 0 | for (var i = 0; i < count; i++) { |
| 885 | 0 | counter.push(i); |
| 886 | } | |
| 887 | 0 | return async.mapSeries(counter, iterator, callback); |
| 888 | }; | |
| 889 | ||
| 890 | 1 | async.compose = function (/* functions... */) { |
| 891 | 0 | var fns = Array.prototype.reverse.call(arguments); |
| 892 | 0 | return function () { |
| 893 | 0 | var that = this; |
| 894 | 0 | var args = Array.prototype.slice.call(arguments); |
| 895 | 0 | var callback = args.pop(); |
| 896 | 0 | async.reduce(fns, args, function (newargs, fn, cb) { |
| 897 | 0 | fn.apply(that, newargs.concat([function () { |
| 898 | 0 | var err = arguments[0]; |
| 899 | 0 | var nextargs = Array.prototype.slice.call(arguments, 1); |
| 900 | 0 | cb(err, nextargs); |
| 901 | }])) | |
| 902 | }, | |
| 903 | function (err, results) { | |
| 904 | 0 | callback.apply(that, [err].concat(results)); |
| 905 | }); | |
| 906 | }; | |
| 907 | }; | |
| 908 | ||
| 909 | 1 | var _applyEach = function (eachfn, fns /*args...*/) { |
| 910 | 0 | var go = function () { |
| 911 | 0 | var that = this; |
| 912 | 0 | var args = Array.prototype.slice.call(arguments); |
| 913 | 0 | var callback = args.pop(); |
| 914 | 0 | return eachfn(fns, function (fn, cb) { |
| 915 | 0 | fn.apply(that, args.concat([cb])); |
| 916 | }, | |
| 917 | callback); | |
| 918 | }; | |
| 919 | 0 | if (arguments.length > 2) { |
| 920 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 921 | 0 | return go.apply(this, args); |
| 922 | } | |
| 923 | else { | |
| 924 | 0 | return go; |
| 925 | } | |
| 926 | }; | |
| 927 | 1 | async.applyEach = doParallel(_applyEach); |
| 928 | 1 | async.applyEachSeries = doSeries(_applyEach); |
| 929 | ||
| 930 | 1 | async.forever = function (fn, callback) { |
| 931 | 0 | function next(err) { |
| 932 | 0 | if (err) { |
| 933 | 0 | if (callback) { |
| 934 | 0 | return callback(err); |
| 935 | } | |
| 936 | 0 | throw err; |
| 937 | } | |
| 938 | 0 | fn(next); |
| 939 | } | |
| 940 | 0 | next(); |
| 941 | }; | |
| 942 | ||
| 943 | // AMD / RequireJS | |
| 944 | 1 | if (typeof define !== 'undefined' && define.amd) { |
| 945 | 0 | define([], function () { |
| 946 | 0 | return async; |
| 947 | }); | |
| 948 | } | |
| 949 | // Node.js | |
| 950 | 1 | else if (typeof module !== 'undefined' && module.exports) { |
| 951 | 1 | module.exports = async; |
| 952 | } | |
| 953 | // included directly via <script> tag | |
| 954 | else { | |
| 955 | 0 | root.async = async; |
| 956 | } | |
| 957 | ||
| 958 | }()); | |
| 959 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Lingo - inflection | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Language = require('./language'); |
| 13 | ||
| 14 | /** | |
| 15 | * Check if a `word` is uncountable. | |
| 16 | * | |
| 17 | * @param {String} word | |
| 18 | * @return {Boolean} | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | Language.prototype.isUncountable = function(word){ |
| 23 | 0 | return !!this.rules.uncountable[word]; |
| 24 | }; | |
| 25 | ||
| 26 | /** | |
| 27 | * Add an uncountable `word`. | |
| 28 | * | |
| 29 | * @param {String} word | |
| 30 | * @return {Language} for chaining | |
| 31 | * @api public | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | Language.prototype.uncountable = function(word){ |
| 35 | 44 | this.rules.uncountable[word] = word; |
| 36 | 44 | return this; |
| 37 | }; | |
| 38 | ||
| 39 | /** | |
| 40 | * Add an irreglar `singular` / `plural` map. | |
| 41 | * | |
| 42 | * @param {String} singular | |
| 43 | * @param {String} plural | |
| 44 | * @return {Language} for chaining | |
| 45 | * @api public | |
| 46 | */ | |
| 47 | ||
| 48 | 1 | Language.prototype.irregular = function(singular, plural){ |
| 49 | 31 | this.rules.irregular.plural[singular] = plural; |
| 50 | 31 | this.rules.irregular.singular[plural] = singular; |
| 51 | 31 | return this; |
| 52 | }; | |
| 53 | ||
| 54 | /** | |
| 55 | * Add a pluralization `rule` for numbers | |
| 56 | * | |
| 57 | * @param {RegExp} rule | |
| 58 | * @return {Language} for chaining | |
| 59 | * @api public | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | Language.prototype.pluralNumbers = function(rule){ |
| 63 | 2 | this.rules.pluralNumbers = rule; |
| 64 | 2 | return this; |
| 65 | }; | |
| 66 | ||
| 67 | /** | |
| 68 | * Add a pluralization `rule` with the given `substitution`. | |
| 69 | * | |
| 70 | * @param {RegExp} rule | |
| 71 | * @param {String} substitution | |
| 72 | * @return {Language} for chaining | |
| 73 | * @api public | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | Language.prototype.plural = function(rule, substitution){ |
| 77 | 31 | this.rules.plural.unshift([rule, substitution]); |
| 78 | 31 | return this; |
| 79 | }; | |
| 80 | ||
| 81 | /** | |
| 82 | * Add a singularization `rule` with the given `substitution`. | |
| 83 | * | |
| 84 | * @param {RegExp} rule | |
| 85 | * @param {String} substitution | |
| 86 | * @return {Language} for chaining | |
| 87 | * @api public | |
| 88 | */ | |
| 89 | ||
| 90 | 1 | Language.prototype.singular = function(rule, substitution){ |
| 91 | 37 | this.rules.singular.unshift([rule, substitution]); |
| 92 | 37 | return this; |
| 93 | }; | |
| 94 | ||
| 95 | /** | |
| 96 | * Pluralize the given `word`. | |
| 97 | * | |
| 98 | * @param {String} word | |
| 99 | * @return {String} | |
| 100 | * @api public | |
| 101 | */ | |
| 102 | ||
| 103 | 1 | Language.prototype.pluralize = function(word){ |
| 104 | 0 | return this.inflect(word, 'plural'); |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Check if `word` is plural. | |
| 109 | * | |
| 110 | * @param {String or Number} word | |
| 111 | * @return {Boolean} | |
| 112 | * @api public | |
| 113 | */ | |
| 114 | ||
| 115 | 1 | Language.prototype.isPlural = function (word) { |
| 116 | 0 | if ('number' == typeof word) { |
| 117 | 0 | return (this.rules.pluralNumbers || /.*/).test(word); |
| 118 | } else { | |
| 119 | 0 | return word == this.pluralize(this.singularize(word)); |
| 120 | } | |
| 121 | }; | |
| 122 | ||
| 123 | /** | |
| 124 | * Singularize the given `word`. | |
| 125 | * | |
| 126 | * @param {String} word | |
| 127 | * @return {String} | |
| 128 | * @api public | |
| 129 | */ | |
| 130 | ||
| 131 | 1 | Language.prototype.singularize = function (word) { |
| 132 | 0 | return this.inflect(word, 'singular'); |
| 133 | }; | |
| 134 | ||
| 135 | /** | |
| 136 | * Check if `word` is singular. | |
| 137 | * | |
| 138 | * @param {String or Number} word | |
| 139 | * @return {Boolean} | |
| 140 | * @api public | |
| 141 | */ | |
| 142 | ||
| 143 | 1 | Language.prototype.isSingular = function (word) { |
| 144 | 0 | return !this.isPlural(word); |
| 145 | }; | |
| 146 | ||
| 147 | ||
| 148 | /** | |
| 149 | * Tableize the given `str`. | |
| 150 | * | |
| 151 | * Examples: | |
| 152 | * | |
| 153 | * lingo.tableize('UserAccount'); | |
| 154 | * // => "user_accounts" | |
| 155 | * | |
| 156 | * lingo.tableize('User'); | |
| 157 | * // => "users" | |
| 158 | * | |
| 159 | * @param {String} str | |
| 160 | * @return {String} | |
| 161 | * @api public | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | exports.tableize = function(str){ |
| 165 | 0 | var underscored = exports.underscore(word); |
| 166 | 0 | return Language.pluralize(underscored); |
| 167 | }; | |
| 168 | ||
| 169 | /** | |
| 170 | * Perform `type` inflection rules on the given `word`. | |
| 171 | * | |
| 172 | * @param {String} word | |
| 173 | * @param {String} type | |
| 174 | * @return {String} | |
| 175 | * @api private | |
| 176 | */ | |
| 177 | ||
| 178 | 1 | Language.prototype.inflect = function(word, type) { |
| 179 | 0 | if (this.isUncountable(word)) return word; |
| 180 | ||
| 181 | 0 | var irregular = this.rules.irregular[type][word]; |
| 182 | 0 | if (irregular) return irregular; |
| 183 | ||
| 184 | 0 | for (var i = 0, len = this.rules[type].length; i < len; ++i) { |
| 185 | 0 | var rule = this.rules[type][i] |
| 186 | , regexp = rule[0] | |
| 187 | , sub = rule[1]; | |
| 188 | 0 | if (regexp.test(word)) { |
| 189 | 0 | return word.replace(regexp, sub); |
| 190 | } | |
| 191 | } | |
| 192 | ||
| 193 | 0 | return word; |
| 194 | } | |
| 195 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Lingo - Language | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var lingo = require('./lingo'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Language` with the given `code` and `name`. | |
| 16 | * | |
| 17 | * @param {String} code | |
| 18 | * @param {String} name | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | var Language = module.exports = function Language(code, name) { |
| 23 | 2 | this.code = code; |
| 24 | 2 | this.name = name; |
| 25 | 2 | this.translations = {}; |
| 26 | 2 | this.rules = { |
| 27 | plural: [] | |
| 28 | , singular: [] | |
| 29 | , uncountable: {} | |
| 30 | , irregular: { plural: {}, singular: {}} | |
| 31 | }; | |
| 32 | 2 | lingo[code] = this; |
| 33 | }; | |
| 34 | ||
| 35 | /** | |
| 36 | * Translate the given `str` with optional `params`. | |
| 37 | * | |
| 38 | * @param {String} str | |
| 39 | * @param {Object} params | |
| 40 | * @return {String} | |
| 41 | * @api public | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | Language.prototype.translate = function(str, params){ |
| 45 | 0 | str = this.translations[str] || str; |
| 46 | 0 | if (params) { |
| 47 | 0 | str = str.replace(/\{([^}]+)\}/g, function(_, key){ |
| 48 | 0 | return params[key]; |
| 49 | }); | |
| 50 | } | |
| 51 | 0 | return str; |
| 52 | }; | |
| 53 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Lingo - languages - English | |
| 3 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 4 | * MIT Licensed | |
| 5 | */ | |
| 6 | ||
| 7 | /** | |
| 8 | * Module dependencies. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | var Language = require('../language'); |
| 12 | ||
| 13 | /** | |
| 14 | * English. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | var en = module.exports = new Language('en', 'English'); |
| 18 | ||
| 19 | /** | |
| 20 | * Number pluraluzation rule | |
| 21 | */ | |
| 22 | 1 | en.pluralNumbers(/[^1]/); |
| 23 | ||
| 24 | /** | |
| 25 | * Default pluralization rules. | |
| 26 | */ | |
| 27 | ||
| 28 | 1 | en.plural(/$/, "s") |
| 29 | .plural(/(s|ss|sh|ch|x|o)$/i, "$1es") | |
| 30 | .plural(/y$/i, "ies") | |
| 31 | .plural(/(o|e)y$/i, "$1ys") | |
| 32 | .plural(/(octop|vir)us$/i, "$1i") | |
| 33 | .plural(/(alias|status)$/i, "$1es") | |
| 34 | .plural(/(bu)s$/i, "$1ses") | |
| 35 | .plural(/([ti])um$/i, "$1a") | |
| 36 | .plural(/sis$/i, "ses") | |
| 37 | .plural(/(?:([^f])fe|([lr])f)$/i, "$1$2ves") | |
| 38 | .plural(/([^aeiouy]|qu)y$/i, "$1ies") | |
| 39 | .plural(/(matr|vert|ind)(?:ix|ex)$/i, "$1ices") | |
| 40 | .plural(/([m|l])ouse$/i, "$1ice") | |
| 41 | .plural(/^(ox)$/i, "$1en") | |
| 42 | .plural(/(quiz)$/i, "$1zes"); | |
| 43 | ||
| 44 | /** | |
| 45 | * Default singularization rules. | |
| 46 | */ | |
| 47 | ||
| 48 | 1 | en.singular(/s$/i, "") |
| 49 | .singular(/(bu|mis|kis)s$/i, "$1s") | |
| 50 | .singular(/([ti])a$/i, "$1um") | |
| 51 | .singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis") | |
| 52 | .singular(/(^analy)ses$/i, "$1sis") | |
| 53 | .singular(/([^f])ves$/i, "$1fe") | |
| 54 | .singular(/([lr])ves$/i, "$1f") | |
| 55 | .singular(/ies$/i, "ie") | |
| 56 | .singular(/([^aeiouy]|qu)ies$/i, "$1y") | |
| 57 | .singular(/(series)$/i, "$1") | |
| 58 | .singular(/(mov)ies$/i, "$1ie") | |
| 59 | .singular(/(x|ch|ss|sh)es$/i, "$1") | |
| 60 | .singular(/([m|l])ice$/i, "$1ouse") | |
| 61 | .singular(/(bus)es$/i, "$1") | |
| 62 | .singular(/(o)es$/i, "$1") | |
| 63 | .singular(/(shoe)s$/i, "$1") | |
| 64 | .singular(/(cris|ax|test)es$/i, "$1is") | |
| 65 | .singular(/(octop|vir)i$/i, "$1us") | |
| 66 | .singular(/(alias|status)es$/i, "$1") | |
| 67 | .singular(/^(ox)en/i, "$1") | |
| 68 | .singular(/(vert|ind)ices$/i, "$1ex") | |
| 69 | .singular(/(matr)ices$/i, "$1ix") | |
| 70 | .singular(/(quiz)zes$/i, "$1"); | |
| 71 | ||
| 72 | /** | |
| 73 | * Default irregular word mappings. | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | en.irregular('i', 'we') |
| 77 | .irregular('person', 'people') | |
| 78 | .irregular('man', 'men') | |
| 79 | .irregular('child', 'children') | |
| 80 | .irregular('move', 'moves') | |
| 81 | .irregular('she', 'they') | |
| 82 | .irregular('he', 'they') | |
| 83 | .irregular('myself', 'ourselves') | |
| 84 | .irregular('yourself', 'ourselves') | |
| 85 | .irregular('himself', 'themselves') | |
| 86 | .irregular('herself', 'themselves') | |
| 87 | .irregular('themself', 'themselves') | |
| 88 | .irregular('mine', 'ours') | |
| 89 | .irregular('hers', 'theirs') | |
| 90 | .irregular('his', 'theirs') | |
| 91 | .irregular('its', 'theirs') | |
| 92 | .irregular('theirs', 'theirs') | |
| 93 | .irregular('sex', 'sexes') | |
| 94 | .irregular('video', 'videos') | |
| 95 | .irregular('rodeo', 'rodeos'); | |
| 96 | ||
| 97 | /** | |
| 98 | * Default uncountables. | |
| 99 | */ | |
| 100 | ||
| 101 | 1 | en.uncountable('advice') |
| 102 | .uncountable('enegery') | |
| 103 | .uncountable('excretion') | |
| 104 | .uncountable('digestion') | |
| 105 | .uncountable('cooperation') | |
| 106 | .uncountable('health') | |
| 107 | .uncountable('justice') | |
| 108 | .uncountable('jeans') | |
| 109 | .uncountable('labour') | |
| 110 | .uncountable('machinery') | |
| 111 | .uncountable('equipment') | |
| 112 | .uncountable('information') | |
| 113 | .uncountable('pollution') | |
| 114 | .uncountable('sewage') | |
| 115 | .uncountable('paper') | |
| 116 | .uncountable('money') | |
| 117 | .uncountable('species') | |
| 118 | .uncountable('series') | |
| 119 | .uncountable('rain') | |
| 120 | .uncountable('rice') | |
| 121 | .uncountable('fish') | |
| 122 | .uncountable('sheep') | |
| 123 | .uncountable('moose') | |
| 124 | .uncountable('deer') | |
| 125 | .uncountable('bison') | |
| 126 | .uncountable('proceedings') | |
| 127 | .uncountable('shears') | |
| 128 | .uncountable('pincers') | |
| 129 | .uncountable('breeches') | |
| 130 | .uncountable('hijinks') | |
| 131 | .uncountable('clippers') | |
| 132 | .uncountable('chassis') | |
| 133 | .uncountable('innings') | |
| 134 | .uncountable('elk') | |
| 135 | .uncountable('rhinoceros') | |
| 136 | .uncountable('swine') | |
| 137 | .uncountable('you') | |
| 138 | .uncountable('news'); | |
| 139 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Lingo - languages - Spanish | |
| 3 | * Copyright(c) 2010 Pau Ramon <masylum@gmail.com> | |
| 4 | * Based on Bermi's Python inflector http://github.com/bermi/Python-Inflector | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Language = require('../language'); |
| 13 | ||
| 14 | /** | |
| 15 | * English. | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | var es = module.exports = new Language('es', 'Español'); |
| 19 | ||
| 20 | /** | |
| 21 | * Number pluraluzation rule | |
| 22 | */ | |
| 23 | 1 | es.pluralNumbers(/[^1]/); |
| 24 | ||
| 25 | /** | |
| 26 | * Default pluralization rules. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | es.plural(/$/, "es") |
| 30 | .plural(/(ng|[wckgtp])$/i, "$1s") | |
| 31 | .plural(/([Ãú])$/i, "$1es") | |
| 32 | .plural(/z$/i, "ces") | |
| 33 | .plural(/([éÃ])(s)$/i, "$1$2es") | |
| 34 | .plural(/([aeiou])s$/i, "$1s") | |
| 35 | .plural(/([aeiouáéó])$/i, "$1s") | |
| 36 | .plural(/(^[bcdfghjklmnñpqrstvwxyz]*)([aeiou])([ns])$/i, "$1$2$3es") | |
| 37 | .plural(/([áéÃóú])s$/i, "$1ses") | |
| 38 | .plural(/(^[bcdfghjklmnñpqrstvwxyz]*)an$/i, "$1anes") | |
| 39 | .plural(/([á])([ns])$/i, "a$2es") | |
| 40 | .plural(/([é])([ns])$/i, "e$2es") | |
| 41 | .plural(/([Ã])([ns])$/i, "i$2es") | |
| 42 | .plural(/([ó])([ns])$/i, "o$2es") | |
| 43 | .plural(/([ú])([ns])$/i, "u$2es") | |
| 44 | .plural(/([aeiou])x$/i, "$1x"); | |
| 45 | ||
| 46 | /** | |
| 47 | * Default singularization rules. | |
| 48 | */ | |
| 49 | ||
| 50 | 1 | es.singular(/es$/i, "") |
| 51 | .singular(/([ghñpv]e)s$/i, "$1") | |
| 52 | .singular(/([bcdfghjklmnñprstvwxyz]{2,}e)s$/i, "$1") | |
| 53 | .singular(/([^e])s$/i, "$1") | |
| 54 | .singular(/(é)s$/i, "$1") | |
| 55 | .singular(/(sis|tis|xis)+$/i, "$1") | |
| 56 | .singular(/(ces)$/i, "z") | |
| 57 | .singular(/oides$/i, "oide") | |
| 58 | .singular(/([a])([ns])es$/i, "á$2") | |
| 59 | .singular(/([e])([ns])es$/i, "é$2") | |
| 60 | .singular(/([i])([ns])es$/i, "Ã$2") | |
| 61 | .singular(/([o])([ns])es$/i, "ó$2") | |
| 62 | .singular(/([u])([ns])es$/i, "ú$2") | |
| 63 | .singular(/^([bcdfghjklmnñpqrstvwxyz]*)([aeiou])([ns])es$/i, "$1$2$3"); | |
| 64 | ||
| 65 | /** | |
| 66 | * Default irregular word mappings. | |
| 67 | */ | |
| 68 | ||
| 69 | 1 | es.irregular('paÃs', 'paÃses') |
| 70 | .irregular('champú', 'champús') | |
| 71 | .irregular('jersey', 'jerséis') | |
| 72 | .irregular('carácter', 'caracteres') | |
| 73 | .irregular('espécimen', 'especÃmenes') | |
| 74 | .irregular('menú', 'menús') | |
| 75 | .irregular('régimen', 'regÃmenes') | |
| 76 | .irregular('curriculum', 'currÃculos') | |
| 77 | .irregular('ultimátum', 'ultimatos') | |
| 78 | .irregular('memorándum', 'memorandos') | |
| 79 | .irregular('referéndum', 'referendos') | |
| 80 | ||
| 81 | /** | |
| 82 | * Default uncountables. | |
| 83 | */ | |
| 84 | ||
| 85 | 1 | es.uncountable('tijeras') |
| 86 | .uncountable('gafas') | |
| 87 | .uncountable('agua') | |
| 88 | .uncountable('vacaciones') | |
| 89 | .uncountable('vÃveres') | |
| 90 | .uncountable('déficit') | |
| 91 | ||
| 92 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Lingo | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var fs = require('fs'); |
| 13 | ||
| 14 | /** | |
| 15 | * Library version. | |
| 16 | * | |
| 17 | * @type String | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | exports.version = '0.0.4'; |
| 21 | ||
| 22 | /** | |
| 23 | * Expose `Language`. | |
| 24 | * | |
| 25 | * @type Function | |
| 26 | */ | |
| 27 | ||
| 28 | 1 | exports.Language = require('./language'); |
| 29 | ||
| 30 | /** | |
| 31 | * Extend `Language` with inflection rules. | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | require('./inflection'); |
| 35 | ||
| 36 | /** | |
| 37 | * Auto-require languages. | |
| 38 | */ | |
| 39 | ||
| 40 | 1 | require('./languages/en'); |
| 41 | 1 | require('./languages/es'); |
| 42 | ||
| 43 | /** | |
| 44 | * Capitalize the first word of `str` or optionally `allWords`. | |
| 45 | * | |
| 46 | * Examples: | |
| 47 | * | |
| 48 | * lingo.capitalize('hello there'); | |
| 49 | * // => "Hello there" | |
| 50 | * | |
| 51 | * lingo.capitalize('hello there', true); | |
| 52 | * // => "Hello There" | |
| 53 | * | |
| 54 | * @param {String} str | |
| 55 | * @param {Boolean} allWords | |
| 56 | * @return {String} | |
| 57 | * @api public | |
| 58 | */ | |
| 59 | ||
| 60 | 1 | exports.capitalize = function(str, allWords){ |
| 61 | 0 | if (allWords) { |
| 62 | 0 | return str.split(' ').map(function(word){ |
| 63 | 0 | return exports.capitalize(word); |
| 64 | }).join(' '); | |
| 65 | } | |
| 66 | 0 | return str.charAt(0).toUpperCase() + str.substr(1); |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Camel-case the given `str`. | |
| 71 | * | |
| 72 | * Examples: | |
| 73 | * | |
| 74 | * lingo.camelcase('foo bar'); | |
| 75 | * // => "fooBar" | |
| 76 | * | |
| 77 | * lingo.camelcase('foo bar baz', true); | |
| 78 | * // => "FooBarBaz" | |
| 79 | * | |
| 80 | * @param {String} str | |
| 81 | * @param {Boolean} uppercaseFirst | |
| 82 | * @return {String} | |
| 83 | * @api public | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | exports.camelcase = function(str, uppercaseFirst){ |
| 87 | 0 | return str.replace(/[^\w\d ]+/g, '').split(' ').map(function(word, i){ |
| 88 | 0 | if (i || (0 == i && uppercaseFirst)) { |
| 89 | 0 | word = exports.capitalize(word); |
| 90 | } | |
| 91 | 0 | return word; |
| 92 | }).join(''); | |
| 93 | }; | |
| 94 | ||
| 95 | /** | |
| 96 | * Underscore the given `str`. | |
| 97 | * | |
| 98 | * Examples: | |
| 99 | * | |
| 100 | * lingo.underscore('UserAccount'); | |
| 101 | * // => "user_account" | |
| 102 | * | |
| 103 | * lingo.underscore('User'); | |
| 104 | * // => "user" | |
| 105 | * | |
| 106 | * @param {String} str | |
| 107 | * @return {String} | |
| 108 | * @api public | |
| 109 | */ | |
| 110 | ||
| 111 | 1 | exports.underscore = function(str){ |
| 112 | 0 | return str.replace(/([a-z\d])([A-Z])/g, '$1_$2').toLowerCase(); |
| 113 | }; | |
| 114 | ||
| 115 | /** | |
| 116 | * Join an array with the given `last` string | |
| 117 | * which defaults to "and". | |
| 118 | * | |
| 119 | * Examples: | |
| 120 | * | |
| 121 | * lingo.join(['fruits', 'veggies', 'sugar']); | |
| 122 | * // => "fruits, veggies and sugar" | |
| 123 | * | |
| 124 | * lingo.join(['fruits', 'veggies', 'sugar'], 'or'); | |
| 125 | * // => "fruits, veggies or sugar" | |
| 126 | * | |
| 127 | * @param {Array} arr | |
| 128 | * @param {String} last | |
| 129 | * @return {String} | |
| 130 | * @api public | |
| 131 | */ | |
| 132 | ||
| 133 | 1 | exports.join = function(arr, last){ |
| 134 | 0 | var str = arr.pop() |
| 135 | , last = last || 'and'; | |
| 136 | 0 | if (arr.length) { |
| 137 | 0 | str = arr.join(', ') + ' ' + last + ' ' + str; |
| 138 | } | |
| 139 | 0 | return str; |
| 140 | }; | |
| 141 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var connect = require('connect') |
| 6 | , Router = require('./router') | |
| 7 | , methods = require('methods') | |
| 8 | , middleware = require('./middleware') | |
| 9 | , debug = require('debug')('express:application') | |
| 10 | , locals = require('./utils').locals | |
| 11 | , View = require('./view') | |
| 12 | , utils = connect.utils | |
| 13 | , http = require('http'); | |
| 14 | ||
| 15 | /** | |
| 16 | * Application prototype. | |
| 17 | */ | |
| 18 | ||
| 19 | 1 | var app = exports = module.exports = {}; |
| 20 | ||
| 21 | /** | |
| 22 | * Initialize the server. | |
| 23 | * | |
| 24 | * - setup default configuration | |
| 25 | * - setup default middleware | |
| 26 | * - setup route reflection methods | |
| 27 | * | |
| 28 | * @api private | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | app.init = function(){ |
| 32 | 1 | this.cache = {}; |
| 33 | 1 | this.settings = {}; |
| 34 | 1 | this.engines = {}; |
| 35 | 1 | this.defaultConfiguration(); |
| 36 | }; | |
| 37 | ||
| 38 | /** | |
| 39 | * Initialize application configuration. | |
| 40 | * | |
| 41 | * @api private | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | app.defaultConfiguration = function(){ |
| 45 | // default settings | |
| 46 | 1 | this.enable('x-powered-by'); |
| 47 | 1 | this.enable('etag'); |
| 48 | 1 | this.set('env', process.env.NODE_ENV || 'development'); |
| 49 | 1 | this.set('subdomain offset', 2); |
| 50 | 1 | debug('booting in %s mode', this.get('env')); |
| 51 | ||
| 52 | // implicit middleware | |
| 53 | 1 | this.use(connect.query()); |
| 54 | 1 | this.use(middleware.init(this)); |
| 55 | ||
| 56 | // inherit protos | |
| 57 | 1 | this.on('mount', function(parent){ |
| 58 | 0 | this.request.__proto__ = parent.request; |
| 59 | 0 | this.response.__proto__ = parent.response; |
| 60 | 0 | this.engines.__proto__ = parent.engines; |
| 61 | 0 | this.settings.__proto__ = parent.settings; |
| 62 | }); | |
| 63 | ||
| 64 | // router | |
| 65 | 1 | this._router = new Router(this); |
| 66 | 1 | this.routes = this._router.map; |
| 67 | 1 | this.__defineGetter__('router', function(){ |
| 68 | 0 | this._usedRouter = true; |
| 69 | 0 | this._router.caseSensitive = this.enabled('case sensitive routing'); |
| 70 | 0 | this._router.strict = this.enabled('strict routing'); |
| 71 | 0 | return this._router.middleware; |
| 72 | }); | |
| 73 | ||
| 74 | // setup locals | |
| 75 | 1 | this.locals = locals(this); |
| 76 | ||
| 77 | // default locals | |
| 78 | 1 | this.locals.settings = this.settings; |
| 79 | ||
| 80 | // default configuration | |
| 81 | 1 | this.set('view', View); |
| 82 | 1 | this.set('views', process.cwd() + '/views'); |
| 83 | 1 | this.set('jsonp callback name', 'callback'); |
| 84 | ||
| 85 | 1 | this.configure('development', function(){ |
| 86 | 1 | this.set('json spaces', 2); |
| 87 | }); | |
| 88 | ||
| 89 | 1 | this.configure('production', function(){ |
| 90 | 0 | this.enable('view cache'); |
| 91 | }); | |
| 92 | }; | |
| 93 | ||
| 94 | /** | |
| 95 | * Proxy `connect#use()` to apply settings to | |
| 96 | * mounted applications. | |
| 97 | * | |
| 98 | * @param {String|Function|Server} route | |
| 99 | * @param {Function|Server} fn | |
| 100 | * @return {app} for chaining | |
| 101 | * @api public | |
| 102 | */ | |
| 103 | ||
| 104 | 1 | app.use = function(route, fn){ |
| 105 | 2 | var app; |
| 106 | ||
| 107 | // default route to '/' | |
| 108 | 4 | if ('string' != typeof route) fn = route, route = '/'; |
| 109 | ||
| 110 | // express app | |
| 111 | 2 | if (fn.handle && fn.set) app = fn; |
| 112 | ||
| 113 | // restore .app property on req and res | |
| 114 | 2 | if (app) { |
| 115 | 0 | app.route = route; |
| 116 | 0 | fn = function(req, res, next) { |
| 117 | 0 | var orig = req.app; |
| 118 | 0 | app.handle(req, res, function(err){ |
| 119 | 0 | req.__proto__ = orig.request; |
| 120 | 0 | res.__proto__ = orig.response; |
| 121 | 0 | next(err); |
| 122 | }); | |
| 123 | }; | |
| 124 | } | |
| 125 | ||
| 126 | 2 | connect.proto.use.call(this, route, fn); |
| 127 | ||
| 128 | // mounted an app | |
| 129 | 2 | if (app) { |
| 130 | 0 | app.parent = this; |
| 131 | 0 | app.emit('mount', this); |
| 132 | } | |
| 133 | ||
| 134 | 2 | return this; |
| 135 | }; | |
| 136 | ||
| 137 | /** | |
| 138 | * Register the given template engine callback `fn` | |
| 139 | * as `ext`. | |
| 140 | * | |
| 141 | * By default will `require()` the engine based on the | |
| 142 | * file extension. For example if you try to render | |
| 143 | * a "foo.jade" file Express will invoke the following internally: | |
| 144 | * | |
| 145 | * app.engine('jade', require('jade').__express); | |
| 146 | * | |
| 147 | * For engines that do not provide `.__express` out of the box, | |
| 148 | * or if you wish to "map" a different extension to the template engine | |
| 149 | * you may use this method. For example mapping the EJS template engine to | |
| 150 | * ".html" files: | |
| 151 | * | |
| 152 | * app.engine('html', require('ejs').renderFile); | |
| 153 | * | |
| 154 | * In this case EJS provides a `.renderFile()` method with | |
| 155 | * the same signature that Express expects: `(path, options, callback)`, | |
| 156 | * though note that it aliases this method as `ejs.__express` internally | |
| 157 | * so if you're using ".ejs" extensions you dont need to do anything. | |
| 158 | * | |
| 159 | * Some template engines do not follow this convention, the | |
| 160 | * [Consolidate.js](https://github.com/visionmedia/consolidate.js) | |
| 161 | * library was created to map all of node's popular template | |
| 162 | * engines to follow this convention, thus allowing them to | |
| 163 | * work seamlessly within Express. | |
| 164 | * | |
| 165 | * @param {String} ext | |
| 166 | * @param {Function} fn | |
| 167 | * @return {app} for chaining | |
| 168 | * @api public | |
| 169 | */ | |
| 170 | ||
| 171 | 1 | app.engine = function(ext, fn){ |
| 172 | 0 | if ('function' != typeof fn) throw new Error('callback function required'); |
| 173 | 0 | if ('.' != ext[0]) ext = '.' + ext; |
| 174 | 0 | this.engines[ext] = fn; |
| 175 | 0 | return this; |
| 176 | }; | |
| 177 | ||
| 178 | /** | |
| 179 | * Map the given param placeholder `name`(s) to the given callback(s). | |
| 180 | * | |
| 181 | * Parameter mapping is used to provide pre-conditions to routes | |
| 182 | * which use normalized placeholders. For example a _:user_id_ parameter | |
| 183 | * could automatically load a user's information from the database without | |
| 184 | * any additional code, | |
| 185 | * | |
| 186 | * The callback uses the same signature as middleware, the only difference | |
| 187 | * being that the value of the placeholder is passed, in this case the _id_ | |
| 188 | * of the user. Once the `next()` function is invoked, just like middleware | |
| 189 | * it will continue on to execute the route, or subsequent parameter functions. | |
| 190 | * | |
| 191 | * app.param('user_id', function(req, res, next, id){ | |
| 192 | * User.find(id, function(err, user){ | |
| 193 | * if (err) { | |
| 194 | * next(err); | |
| 195 | * } else if (user) { | |
| 196 | * req.user = user; | |
| 197 | * next(); | |
| 198 | * } else { | |
| 199 | * next(new Error('failed to load user')); | |
| 200 | * } | |
| 201 | * }); | |
| 202 | * }); | |
| 203 | * | |
| 204 | * @param {String|Array} name | |
| 205 | * @param {Function} fn | |
| 206 | * @return {app} for chaining | |
| 207 | * @api public | |
| 208 | */ | |
| 209 | ||
| 210 | 1 | app.param = function(name, fn){ |
| 211 | 0 | var self = this |
| 212 | , fns = [].slice.call(arguments, 1); | |
| 213 | ||
| 214 | // array | |
| 215 | 0 | if (Array.isArray(name)) { |
| 216 | 0 | name.forEach(function(name){ |
| 217 | 0 | fns.forEach(function(fn){ |
| 218 | 0 | self.param(name, fn); |
| 219 | }); | |
| 220 | }); | |
| 221 | // param logic | |
| 222 | 0 | } else if ('function' == typeof name) { |
| 223 | 0 | this._router.param(name); |
| 224 | // single | |
| 225 | } else { | |
| 226 | 0 | if (':' == name[0]) name = name.substr(1); |
| 227 | 0 | fns.forEach(function(fn){ |
| 228 | 0 | self._router.param(name, fn); |
| 229 | }); | |
| 230 | } | |
| 231 | ||
| 232 | 0 | return this; |
| 233 | }; | |
| 234 | ||
| 235 | /** | |
| 236 | * Assign `setting` to `val`, or return `setting`'s value. | |
| 237 | * | |
| 238 | * app.set('foo', 'bar'); | |
| 239 | * app.get('foo'); | |
| 240 | * // => "bar" | |
| 241 | * | |
| 242 | * Mounted servers inherit their parent server's settings. | |
| 243 | * | |
| 244 | * @param {String} setting | |
| 245 | * @param {String} val | |
| 246 | * @return {Server} for chaining | |
| 247 | * @api public | |
| 248 | */ | |
| 249 | ||
| 250 | 1 | app.set = function(setting, val){ |
| 251 | 10 | if (1 == arguments.length) { |
| 252 | 1 | return this.settings[setting]; |
| 253 | } else { | |
| 254 | 9 | this.settings[setting] = val; |
| 255 | 9 | return this; |
| 256 | } | |
| 257 | }; | |
| 258 | ||
| 259 | /** | |
| 260 | * Return the app's absolute pathname | |
| 261 | * based on the parent(s) that have | |
| 262 | * mounted it. | |
| 263 | * | |
| 264 | * For example if the application was | |
| 265 | * mounted as "/admin", which itself | |
| 266 | * was mounted as "/blog" then the | |
| 267 | * return value would be "/blog/admin". | |
| 268 | * | |
| 269 | * @return {String} | |
| 270 | * @api private | |
| 271 | */ | |
| 272 | ||
| 273 | 1 | app.path = function(){ |
| 274 | 0 | return this.parent |
| 275 | ? this.parent.path() + this.route | |
| 276 | : ''; | |
| 277 | }; | |
| 278 | ||
| 279 | /** | |
| 280 | * Check if `setting` is enabled (truthy). | |
| 281 | * | |
| 282 | * app.enabled('foo') | |
| 283 | * // => false | |
| 284 | * | |
| 285 | * app.enable('foo') | |
| 286 | * app.enabled('foo') | |
| 287 | * // => true | |
| 288 | * | |
| 289 | * @param {String} setting | |
| 290 | * @return {Boolean} | |
| 291 | * @api public | |
| 292 | */ | |
| 293 | ||
| 294 | 1 | app.enabled = function(setting){ |
| 295 | 0 | return !!this.set(setting); |
| 296 | }; | |
| 297 | ||
| 298 | /** | |
| 299 | * Check if `setting` is disabled. | |
| 300 | * | |
| 301 | * app.disabled('foo') | |
| 302 | * // => true | |
| 303 | * | |
| 304 | * app.enable('foo') | |
| 305 | * app.disabled('foo') | |
| 306 | * // => false | |
| 307 | * | |
| 308 | * @param {String} setting | |
| 309 | * @return {Boolean} | |
| 310 | * @api public | |
| 311 | */ | |
| 312 | ||
| 313 | 1 | app.disabled = function(setting){ |
| 314 | 0 | return !this.set(setting); |
| 315 | }; | |
| 316 | ||
| 317 | /** | |
| 318 | * Enable `setting`. | |
| 319 | * | |
| 320 | * @param {String} setting | |
| 321 | * @return {app} for chaining | |
| 322 | * @api public | |
| 323 | */ | |
| 324 | ||
| 325 | 1 | app.enable = function(setting){ |
| 326 | 2 | return this.set(setting, true); |
| 327 | }; | |
| 328 | ||
| 329 | /** | |
| 330 | * Disable `setting`. | |
| 331 | * | |
| 332 | * @param {String} setting | |
| 333 | * @return {app} for chaining | |
| 334 | * @api public | |
| 335 | */ | |
| 336 | ||
| 337 | 1 | app.disable = function(setting){ |
| 338 | 0 | return this.set(setting, false); |
| 339 | }; | |
| 340 | ||
| 341 | /** | |
| 342 | * Configure callback for zero or more envs, | |
| 343 | * when no `env` is specified that callback will | |
| 344 | * be invoked for all environments. Any combination | |
| 345 | * can be used multiple times, in any order desired. | |
| 346 | * | |
| 347 | * Examples: | |
| 348 | * | |
| 349 | * app.configure(function(){ | |
| 350 | * // executed for all envs | |
| 351 | * }); | |
| 352 | * | |
| 353 | * app.configure('stage', function(){ | |
| 354 | * // executed staging env | |
| 355 | * }); | |
| 356 | * | |
| 357 | * app.configure('stage', 'production', function(){ | |
| 358 | * // executed for stage and production | |
| 359 | * }); | |
| 360 | * | |
| 361 | * Note: | |
| 362 | * | |
| 363 | * These callbacks are invoked immediately, and | |
| 364 | * are effectively sugar for the following: | |
| 365 | * | |
| 366 | * var env = process.env.NODE_ENV || 'development'; | |
| 367 | * | |
| 368 | * switch (env) { | |
| 369 | * case 'development': | |
| 370 | * ... | |
| 371 | * break; | |
| 372 | * case 'stage': | |
| 373 | * ... | |
| 374 | * break; | |
| 375 | * case 'production': | |
| 376 | * ... | |
| 377 | * break; | |
| 378 | * } | |
| 379 | * | |
| 380 | * @param {String} env... | |
| 381 | * @param {Function} fn | |
| 382 | * @return {app} for chaining | |
| 383 | * @api public | |
| 384 | */ | |
| 385 | ||
| 386 | 1 | app.configure = function(env, fn){ |
| 387 | 2 | var envs = 'all' |
| 388 | , args = [].slice.call(arguments); | |
| 389 | 2 | fn = args.pop(); |
| 390 | 4 | if (args.length) envs = args; |
| 391 | 3 | if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this); |
| 392 | 2 | return this; |
| 393 | }; | |
| 394 | ||
| 395 | /** | |
| 396 | * Delegate `.VERB(...)` calls to `router.VERB(...)`. | |
| 397 | */ | |
| 398 | ||
| 399 | 1 | methods.forEach(function(method){ |
| 400 | 24 | app[method] = function(path){ |
| 401 | 2 | if ('get' == method && 1 == arguments.length) return this.set(path); |
| 402 | ||
| 403 | // deprecated | |
| 404 | 0 | if (Array.isArray(path)) { |
| 405 | 0 | console.trace('passing an array to app.VERB() is deprecated and will be removed in 4.0'); |
| 406 | } | |
| 407 | ||
| 408 | // if no router attached yet, attach the router | |
| 409 | 0 | if (!this._usedRouter) this.use(this.router); |
| 410 | ||
| 411 | // setup route | |
| 412 | 0 | this._router[method].apply(this._router, arguments); |
| 413 | 0 | return this; |
| 414 | }; | |
| 415 | }); | |
| 416 | ||
| 417 | /** | |
| 418 | * Special-cased "all" method, applying the given route `path`, | |
| 419 | * middleware, and callback to _every_ HTTP method. | |
| 420 | * | |
| 421 | * @param {String} path | |
| 422 | * @param {Function} ... | |
| 423 | * @return {app} for chaining | |
| 424 | * @api public | |
| 425 | */ | |
| 426 | ||
| 427 | 1 | app.all = function(path){ |
| 428 | 0 | var args = arguments; |
| 429 | 0 | methods.forEach(function(method){ |
| 430 | 0 | app[method].apply(this, args); |
| 431 | }, this); | |
| 432 | 0 | return this; |
| 433 | }; | |
| 434 | ||
| 435 | // del -> delete alias | |
| 436 | ||
| 437 | 1 | app.del = app.delete; |
| 438 | ||
| 439 | /** | |
| 440 | * Render the given view `name` name with `options` | |
| 441 | * and a callback accepting an error and the | |
| 442 | * rendered template string. | |
| 443 | * | |
| 444 | * Example: | |
| 445 | * | |
| 446 | * app.render('email', { name: 'Tobi' }, function(err, html){ | |
| 447 | * // ... | |
| 448 | * }) | |
| 449 | * | |
| 450 | * @param {String} name | |
| 451 | * @param {String|Function} options or fn | |
| 452 | * @param {Function} fn | |
| 453 | * @api public | |
| 454 | */ | |
| 455 | ||
| 456 | 1 | app.render = function(name, options, fn){ |
| 457 | 0 | var opts = {} |
| 458 | , cache = this.cache | |
| 459 | , engines = this.engines | |
| 460 | , view; | |
| 461 | ||
| 462 | // support callback function as second arg | |
| 463 | 0 | if ('function' == typeof options) { |
| 464 | 0 | fn = options, options = {}; |
| 465 | } | |
| 466 | ||
| 467 | // merge app.locals | |
| 468 | 0 | utils.merge(opts, this.locals); |
| 469 | ||
| 470 | // merge options._locals | |
| 471 | 0 | if (options._locals) utils.merge(opts, options._locals); |
| 472 | ||
| 473 | // merge options | |
| 474 | 0 | utils.merge(opts, options); |
| 475 | ||
| 476 | // set .cache unless explicitly provided | |
| 477 | 0 | opts.cache = null == opts.cache |
| 478 | ? this.enabled('view cache') | |
| 479 | : opts.cache; | |
| 480 | ||
| 481 | // primed cache | |
| 482 | 0 | if (opts.cache) view = cache[name]; |
| 483 | ||
| 484 | // view | |
| 485 | 0 | if (!view) { |
| 486 | 0 | view = new (this.get('view'))(name, { |
| 487 | defaultEngine: this.get('view engine'), | |
| 488 | root: this.get('views'), | |
| 489 | engines: engines | |
| 490 | }); | |
| 491 | ||
| 492 | 0 | if (!view.path) { |
| 493 | 0 | var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"'); |
| 494 | 0 | err.view = view; |
| 495 | 0 | return fn(err); |
| 496 | } | |
| 497 | ||
| 498 | // prime the cache | |
| 499 | 0 | if (opts.cache) cache[name] = view; |
| 500 | } | |
| 501 | ||
| 502 | // render | |
| 503 | 0 | try { |
| 504 | 0 | view.render(opts, fn); |
| 505 | } catch (err) { | |
| 506 | 0 | fn(err); |
| 507 | } | |
| 508 | }; | |
| 509 | ||
| 510 | /** | |
| 511 | * Listen for connections. | |
| 512 | * | |
| 513 | * A node `http.Server` is returned, with this | |
| 514 | * application (which is a `Function`) as its | |
| 515 | * callback. If you wish to create both an HTTP | |
| 516 | * and HTTPS server you may do so with the "http" | |
| 517 | * and "https" modules as shown here: | |
| 518 | * | |
| 519 | * var http = require('http') | |
| 520 | * , https = require('https') | |
| 521 | * , express = require('express') | |
| 522 | * , app = express(); | |
| 523 | * | |
| 524 | * http.createServer(app).listen(80); | |
| 525 | * https.createServer({ ... }, app).listen(443); | |
| 526 | * | |
| 527 | * @return {http.Server} | |
| 528 | * @api public | |
| 529 | */ | |
| 530 | ||
| 531 | 1 | app.listen = function(){ |
| 532 | 0 | var server = http.createServer(this); |
| 533 | 0 | return server.listen.apply(server, arguments); |
| 534 | }; | |
| 535 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var merge = require('merge-descriptors'); |
| 6 | 1 | var connect = require('connect') |
| 7 | , proto = require('./application') | |
| 8 | , Route = require('./router/route') | |
| 9 | , Router = require('./router') | |
| 10 | , req = require('./request') | |
| 11 | , res = require('./response') | |
| 12 | , utils = connect.utils; | |
| 13 | ||
| 14 | /** | |
| 15 | * Expose `createApplication()`. | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | exports = module.exports = createApplication; |
| 19 | ||
| 20 | /** | |
| 21 | * Expose mime. | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | exports.mime = connect.mime; |
| 25 | ||
| 26 | /** | |
| 27 | * Create an express application. | |
| 28 | * | |
| 29 | * @return {Function} | |
| 30 | * @api public | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | function createApplication() { |
| 34 | 1 | var app = connect(); |
| 35 | 1 | utils.merge(app, proto); |
| 36 | 1 | app.request = { __proto__: req, app: app }; |
| 37 | 1 | app.response = { __proto__: res, app: app }; |
| 38 | 1 | app.init(); |
| 39 | 1 | return app; |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * Expose connect.middleware as express.* | |
| 44 | * for example `express.logger` etc. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | merge(exports, connect.middleware); |
| 48 | ||
| 49 | /** | |
| 50 | * Error on createServer(). | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | exports.createServer = function(){ |
| 54 | 0 | console.warn('Warning: express.createServer() is deprecated, express'); |
| 55 | 0 | console.warn('applications no longer inherit from http.Server,'); |
| 56 | 0 | console.warn('please use:'); |
| 57 | 0 | console.warn(''); |
| 58 | 0 | console.warn(' var express = require("express");'); |
| 59 | 0 | console.warn(' var app = express();'); |
| 60 | 0 | console.warn(''); |
| 61 | 0 | return createApplication(); |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Expose the prototypes. | |
| 66 | */ | |
| 67 | ||
| 68 | 1 | exports.application = proto; |
| 69 | 1 | exports.request = req; |
| 70 | 1 | exports.response = res; |
| 71 | ||
| 72 | /** | |
| 73 | * Expose constructors. | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | exports.Route = Route; |
| 77 | 1 | exports.Router = Router; |
| 78 | ||
| 79 | // Error handler title | |
| 80 | ||
| 81 | 1 | exports.errorHandler.title = 'Express'; |
| 82 | ||
| 83 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('./utils'); |
| 7 | ||
| 8 | /** | |
| 9 | * Initialization middleware, exposing the | |
| 10 | * request and response to eachother, as well | |
| 11 | * as defaulting the X-Powered-By header field. | |
| 12 | * | |
| 13 | * @param {Function} app | |
| 14 | * @return {Function} | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | exports.init = function(app){ |
| 19 | 1 | return function expressInit(req, res, next){ |
| 20 | 0 | if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express'); |
| 21 | 0 | req.res = res; |
| 22 | 0 | res.req = req; |
| 23 | 0 | req.next = next; |
| 24 | ||
| 25 | 0 | req.__proto__ = app.request; |
| 26 | 0 | res.__proto__ = app.response; |
| 27 | ||
| 28 | 0 | res.locals = res.locals || utils.locals(res); |
| 29 | ||
| 30 | 0 | next(); |
| 31 | } | |
| 32 | }; | |
| 33 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var http = require('http') |
| 7 | , utils = require('./utils') | |
| 8 | , connect = require('connect') | |
| 9 | , fresh = require('fresh') | |
| 10 | , parseRange = require('range-parser') | |
| 11 | , parse = connect.utils.parseUrl | |
| 12 | , mime = connect.mime; | |
| 13 | ||
| 14 | /** | |
| 15 | * Request prototype. | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | var req = exports = module.exports = { |
| 19 | __proto__: http.IncomingMessage.prototype | |
| 20 | }; | |
| 21 | ||
| 22 | /** | |
| 23 | * Return request header. | |
| 24 | * | |
| 25 | * The `Referrer` header field is special-cased, | |
| 26 | * both `Referrer` and `Referer` are interchangeable. | |
| 27 | * | |
| 28 | * Examples: | |
| 29 | * | |
| 30 | * req.get('Content-Type'); | |
| 31 | * // => "text/plain" | |
| 32 | * | |
| 33 | * req.get('content-type'); | |
| 34 | * // => "text/plain" | |
| 35 | * | |
| 36 | * req.get('Something'); | |
| 37 | * // => undefined | |
| 38 | * | |
| 39 | * Aliased as `req.header()`. | |
| 40 | * | |
| 41 | * @param {String} name | |
| 42 | * @return {String} | |
| 43 | * @api public | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | req.get = |
| 47 | req.header = function(name){ | |
| 48 | 0 | switch (name = name.toLowerCase()) { |
| 49 | case 'referer': | |
| 50 | case 'referrer': | |
| 51 | 0 | return this.headers.referrer |
| 52 | || this.headers.referer; | |
| 53 | default: | |
| 54 | 0 | return this.headers[name]; |
| 55 | } | |
| 56 | }; | |
| 57 | ||
| 58 | /** | |
| 59 | * Check if the given `type(s)` is acceptable, returning | |
| 60 | * the best match when true, otherwise `undefined`, in which | |
| 61 | * case you should respond with 406 "Not Acceptable". | |
| 62 | * | |
| 63 | * The `type` value may be a single mime type string | |
| 64 | * such as "application/json", the extension name | |
| 65 | * such as "json", a comma-delimted list such as "json, html, text/plain", | |
| 66 | * an argument list such as `"json", "html", "text/plain"`, | |
| 67 | * or an array `["json", "html", "text/plain"]`. When a list | |
| 68 | * or array is given the _best_ match, if any is returned. | |
| 69 | * | |
| 70 | * Examples: | |
| 71 | * | |
| 72 | * // Accept: text/html | |
| 73 | * req.accepts('html'); | |
| 74 | * // => "html" | |
| 75 | * | |
| 76 | * // Accept: text/*, application/json | |
| 77 | * req.accepts('html'); | |
| 78 | * // => "html" | |
| 79 | * req.accepts('text/html'); | |
| 80 | * // => "text/html" | |
| 81 | * req.accepts('json, text'); | |
| 82 | * // => "json" | |
| 83 | * req.accepts('application/json'); | |
| 84 | * // => "application/json" | |
| 85 | * | |
| 86 | * // Accept: text/*, application/json | |
| 87 | * req.accepts('image/png'); | |
| 88 | * req.accepts('png'); | |
| 89 | * // => undefined | |
| 90 | * | |
| 91 | * // Accept: text/*;q=.5, application/json | |
| 92 | * req.accepts(['html', 'json']); | |
| 93 | * req.accepts('html', 'json'); | |
| 94 | * req.accepts('html, json'); | |
| 95 | * // => "json" | |
| 96 | * | |
| 97 | * @param {String|Array} type(s) | |
| 98 | * @return {String} | |
| 99 | * @api public | |
| 100 | */ | |
| 101 | ||
| 102 | 1 | req.accepts = function(type){ |
| 103 | 0 | var args = arguments.length > 1 ? [].slice.apply(arguments) : type; |
| 104 | 0 | return utils.accepts(args, this.get('Accept')); |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Check if the given `encoding` is accepted. | |
| 109 | * | |
| 110 | * @param {String} encoding | |
| 111 | * @return {Boolean} | |
| 112 | * @api public | |
| 113 | */ | |
| 114 | ||
| 115 | 1 | req.acceptsEncoding = function(encoding){ |
| 116 | 0 | return !! ~this.acceptedEncodings.indexOf(encoding); |
| 117 | }; | |
| 118 | ||
| 119 | /** | |
| 120 | * Check if the given `charset` is acceptable, | |
| 121 | * otherwise you should respond with 406 "Not Acceptable". | |
| 122 | * | |
| 123 | * @param {String} charset | |
| 124 | * @return {Boolean} | |
| 125 | * @api public | |
| 126 | */ | |
| 127 | ||
| 128 | 1 | req.acceptsCharset = function(charset){ |
| 129 | 0 | var accepted = this.acceptedCharsets; |
| 130 | 0 | return accepted.length |
| 131 | ? !! ~accepted.indexOf(charset) | |
| 132 | : true; | |
| 133 | }; | |
| 134 | ||
| 135 | /** | |
| 136 | * Check if the given `lang` is acceptable, | |
| 137 | * otherwise you should respond with 406 "Not Acceptable". | |
| 138 | * | |
| 139 | * @param {String} lang | |
| 140 | * @return {Boolean} | |
| 141 | * @api public | |
| 142 | */ | |
| 143 | ||
| 144 | 1 | req.acceptsLanguage = function(lang){ |
| 145 | 0 | var accepted = this.acceptedLanguages; |
| 146 | 0 | return accepted.length |
| 147 | ? !! ~accepted.indexOf(lang) | |
| 148 | : true; | |
| 149 | }; | |
| 150 | ||
| 151 | /** | |
| 152 | * Parse Range header field, | |
| 153 | * capping to the given `size`. | |
| 154 | * | |
| 155 | * Unspecified ranges such as "0-" require | |
| 156 | * knowledge of your resource length. In | |
| 157 | * the case of a byte range this is of course | |
| 158 | * the total number of bytes. If the Range | |
| 159 | * header field is not given `null` is returned, | |
| 160 | * `-1` when unsatisfiable, `-2` when syntactically invalid. | |
| 161 | * | |
| 162 | * NOTE: remember that ranges are inclusive, so | |
| 163 | * for example "Range: users=0-3" should respond | |
| 164 | * with 4 users when available, not 3. | |
| 165 | * | |
| 166 | * @param {Number} size | |
| 167 | * @return {Array} | |
| 168 | * @api public | |
| 169 | */ | |
| 170 | ||
| 171 | 1 | req.range = function(size){ |
| 172 | 0 | var range = this.get('Range'); |
| 173 | 0 | if (!range) return; |
| 174 | 0 | return parseRange(size, range); |
| 175 | }; | |
| 176 | ||
| 177 | /** | |
| 178 | * Return an array of encodings. | |
| 179 | * | |
| 180 | * Examples: | |
| 181 | * | |
| 182 | * ['gzip', 'deflate'] | |
| 183 | * | |
| 184 | * @return {Array} | |
| 185 | * @api public | |
| 186 | */ | |
| 187 | ||
| 188 | 1 | req.__defineGetter__('acceptedEncodings', function(){ |
| 189 | 0 | var accept = this.get('Accept-Encoding'); |
| 190 | 0 | return accept |
| 191 | ? accept.trim().split(/ *, */) | |
| 192 | : []; | |
| 193 | }); | |
| 194 | ||
| 195 | /** | |
| 196 | * Return an array of Accepted media types | |
| 197 | * ordered from highest quality to lowest. | |
| 198 | * | |
| 199 | * Examples: | |
| 200 | * | |
| 201 | * [ { value: 'application/json', | |
| 202 | * quality: 1, | |
| 203 | * type: 'application', | |
| 204 | * subtype: 'json' }, | |
| 205 | * { value: 'text/html', | |
| 206 | * quality: 0.5, | |
| 207 | * type: 'text', | |
| 208 | * subtype: 'html' } ] | |
| 209 | * | |
| 210 | * @return {Array} | |
| 211 | * @api public | |
| 212 | */ | |
| 213 | ||
| 214 | 1 | req.__defineGetter__('accepted', function(){ |
| 215 | 0 | var accept = this.get('Accept'); |
| 216 | 0 | return accept |
| 217 | ? utils.parseAccept(accept) | |
| 218 | : []; | |
| 219 | }); | |
| 220 | ||
| 221 | /** | |
| 222 | * Return an array of Accepted languages | |
| 223 | * ordered from highest quality to lowest. | |
| 224 | * | |
| 225 | * Examples: | |
| 226 | * | |
| 227 | * Accept-Language: en;q=.5, en-us | |
| 228 | * ['en-us', 'en'] | |
| 229 | * | |
| 230 | * @return {Array} | |
| 231 | * @api public | |
| 232 | */ | |
| 233 | ||
| 234 | 1 | req.__defineGetter__('acceptedLanguages', function(){ |
| 235 | 0 | var accept = this.get('Accept-Language'); |
| 236 | 0 | return accept |
| 237 | ? utils | |
| 238 | .parseParams(accept) | |
| 239 | .map(function(obj){ | |
| 240 | 0 | return obj.value; |
| 241 | }) | |
| 242 | : []; | |
| 243 | }); | |
| 244 | ||
| 245 | /** | |
| 246 | * Return an array of Accepted charsets | |
| 247 | * ordered from highest quality to lowest. | |
| 248 | * | |
| 249 | * Examples: | |
| 250 | * | |
| 251 | * Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8 | |
| 252 | * ['unicode-1-1', 'iso-8859-5'] | |
| 253 | * | |
| 254 | * @return {Array} | |
| 255 | * @api public | |
| 256 | */ | |
| 257 | ||
| 258 | 1 | req.__defineGetter__('acceptedCharsets', function(){ |
| 259 | 0 | var accept = this.get('Accept-Charset'); |
| 260 | 0 | return accept |
| 261 | ? utils | |
| 262 | .parseParams(accept) | |
| 263 | .map(function(obj){ | |
| 264 | 0 | return obj.value; |
| 265 | }) | |
| 266 | : []; | |
| 267 | }); | |
| 268 | ||
| 269 | /** | |
| 270 | * Return the value of param `name` when present or `defaultValue`. | |
| 271 | * | |
| 272 | * - Checks route placeholders, ex: _/user/:id_ | |
| 273 | * - Checks body params, ex: id=12, {"id":12} | |
| 274 | * - Checks query string params, ex: ?id=12 | |
| 275 | * | |
| 276 | * To utilize request bodies, `req.body` | |
| 277 | * should be an object. This can be done by using | |
| 278 | * the `connect.bodyParser()` middleware. | |
| 279 | * | |
| 280 | * @param {String} name | |
| 281 | * @param {Mixed} [defaultValue] | |
| 282 | * @return {String} | |
| 283 | * @api public | |
| 284 | */ | |
| 285 | ||
| 286 | 1 | req.param = function(name, defaultValue){ |
| 287 | 0 | var params = this.params || {}; |
| 288 | 0 | var body = this.body || {}; |
| 289 | 0 | var query = this.query || {}; |
| 290 | 0 | if (null != params[name] && params.hasOwnProperty(name)) return params[name]; |
| 291 | 0 | if (null != body[name]) return body[name]; |
| 292 | 0 | if (null != query[name]) return query[name]; |
| 293 | 0 | return defaultValue; |
| 294 | }; | |
| 295 | ||
| 296 | /** | |
| 297 | * Check if the incoming request contains the "Content-Type" | |
| 298 | * header field, and it contains the give mime `type`. | |
| 299 | * | |
| 300 | * Examples: | |
| 301 | * | |
| 302 | * // With Content-Type: text/html; charset=utf-8 | |
| 303 | * req.is('html'); | |
| 304 | * req.is('text/html'); | |
| 305 | * req.is('text/*'); | |
| 306 | * // => true | |
| 307 | * | |
| 308 | * // When Content-Type is application/json | |
| 309 | * req.is('json'); | |
| 310 | * req.is('application/json'); | |
| 311 | * req.is('application/*'); | |
| 312 | * // => true | |
| 313 | * | |
| 314 | * req.is('html'); | |
| 315 | * // => false | |
| 316 | * | |
| 317 | * @param {String} type | |
| 318 | * @return {Boolean} | |
| 319 | * @api public | |
| 320 | */ | |
| 321 | ||
| 322 | 1 | req.is = function(type){ |
| 323 | 0 | var ct = this.get('Content-Type'); |
| 324 | 0 | if (!ct) return false; |
| 325 | 0 | ct = ct.split(';')[0]; |
| 326 | 0 | if (!~type.indexOf('/')) type = mime.lookup(type); |
| 327 | 0 | if (~type.indexOf('*')) { |
| 328 | 0 | type = type.split('/'); |
| 329 | 0 | ct = ct.split('/'); |
| 330 | 0 | if ('*' == type[0] && type[1] == ct[1]) return true; |
| 331 | 0 | if ('*' == type[1] && type[0] == ct[0]) return true; |
| 332 | 0 | return false; |
| 333 | } | |
| 334 | 0 | return !! ~ct.indexOf(type); |
| 335 | }; | |
| 336 | ||
| 337 | /** | |
| 338 | * Return the protocol string "http" or "https" | |
| 339 | * when requested with TLS. When the "trust proxy" | |
| 340 | * setting is enabled the "X-Forwarded-Proto" header | |
| 341 | * field will be trusted. If you're running behind | |
| 342 | * a reverse proxy that supplies https for you this | |
| 343 | * may be enabled. | |
| 344 | * | |
| 345 | * @return {String} | |
| 346 | * @api public | |
| 347 | */ | |
| 348 | ||
| 349 | 1 | req.__defineGetter__('protocol', function(){ |
| 350 | 0 | var trustProxy = this.app.get('trust proxy'); |
| 351 | 0 | if (this.connection.encrypted) return 'https'; |
| 352 | 0 | if (!trustProxy) return 'http'; |
| 353 | 0 | var proto = this.get('X-Forwarded-Proto') || 'http'; |
| 354 | 0 | return proto.split(/\s*,\s*/)[0]; |
| 355 | }); | |
| 356 | ||
| 357 | /** | |
| 358 | * Short-hand for: | |
| 359 | * | |
| 360 | * req.protocol == 'https' | |
| 361 | * | |
| 362 | * @return {Boolean} | |
| 363 | * @api public | |
| 364 | */ | |
| 365 | ||
| 366 | 1 | req.__defineGetter__('secure', function(){ |
| 367 | 0 | return 'https' == this.protocol; |
| 368 | }); | |
| 369 | ||
| 370 | /** | |
| 371 | * Return the remote address, or when | |
| 372 | * "trust proxy" is `true` return | |
| 373 | * the upstream addr. | |
| 374 | * | |
| 375 | * @return {String} | |
| 376 | * @api public | |
| 377 | */ | |
| 378 | ||
| 379 | 1 | req.__defineGetter__('ip', function(){ |
| 380 | 0 | return this.ips[0] || this.connection.remoteAddress; |
| 381 | }); | |
| 382 | ||
| 383 | /** | |
| 384 | * When "trust proxy" is `true`, parse | |
| 385 | * the "X-Forwarded-For" ip address list. | |
| 386 | * | |
| 387 | * For example if the value were "client, proxy1, proxy2" | |
| 388 | * you would receive the array `["client", "proxy1", "proxy2"]` | |
| 389 | * where "proxy2" is the furthest down-stream. | |
| 390 | * | |
| 391 | * @return {Array} | |
| 392 | * @api public | |
| 393 | */ | |
| 394 | ||
| 395 | 1 | req.__defineGetter__('ips', function(){ |
| 396 | 0 | var trustProxy = this.app.get('trust proxy'); |
| 397 | 0 | var val = this.get('X-Forwarded-For'); |
| 398 | 0 | return trustProxy && val |
| 399 | ? val.split(/ *, */) | |
| 400 | : []; | |
| 401 | }); | |
| 402 | ||
| 403 | /** | |
| 404 | * Return basic auth credentials. | |
| 405 | * | |
| 406 | * Examples: | |
| 407 | * | |
| 408 | * // http://tobi:hello@example.com | |
| 409 | * req.auth | |
| 410 | * // => { username: 'tobi', password: 'hello' } | |
| 411 | * | |
| 412 | * @return {Object} or undefined | |
| 413 | * @api public | |
| 414 | */ | |
| 415 | ||
| 416 | 1 | req.__defineGetter__('auth', function(){ |
| 417 | // missing | |
| 418 | 0 | var auth = this.get('Authorization'); |
| 419 | 0 | if (!auth) return; |
| 420 | ||
| 421 | // malformed | |
| 422 | 0 | var parts = auth.split(' '); |
| 423 | 0 | if ('basic' != parts[0].toLowerCase()) return; |
| 424 | 0 | if (!parts[1]) return; |
| 425 | 0 | auth = parts[1]; |
| 426 | ||
| 427 | // credentials | |
| 428 | 0 | auth = new Buffer(auth, 'base64').toString().match(/^([^:]*):(.*)$/); |
| 429 | 0 | if (!auth) return; |
| 430 | 0 | return { username: auth[1], password: auth[2] }; |
| 431 | }); | |
| 432 | ||
| 433 | /** | |
| 434 | * Return subdomains as an array. | |
| 435 | * | |
| 436 | * Subdomains are the dot-separated parts of the host before the main domain of | |
| 437 | * the app. By default, the domain of the app is assumed to be the last two | |
| 438 | * parts of the host. This can be changed by setting "subdomain offset". | |
| 439 | * | |
| 440 | * For example, if the domain is "tobi.ferrets.example.com": | |
| 441 | * If "subdomain offset" is not set, req.subdomains is `["ferrets", "tobi"]`. | |
| 442 | * If "subdomain offset" is 3, req.subdomains is `["tobi"]`. | |
| 443 | * | |
| 444 | * @return {Array} | |
| 445 | * @api public | |
| 446 | */ | |
| 447 | ||
| 448 | 1 | req.__defineGetter__('subdomains', function(){ |
| 449 | 0 | var offset = this.app.get('subdomain offset'); |
| 450 | 0 | return (this.host || '') |
| 451 | .split('.') | |
| 452 | .reverse() | |
| 453 | .slice(offset); | |
| 454 | }); | |
| 455 | ||
| 456 | /** | |
| 457 | * Short-hand for `url.parse(req.url).pathname`. | |
| 458 | * | |
| 459 | * @return {String} | |
| 460 | * @api public | |
| 461 | */ | |
| 462 | ||
| 463 | 1 | req.__defineGetter__('path', function(){ |
| 464 | 0 | return parse(this).pathname; |
| 465 | }); | |
| 466 | ||
| 467 | /** | |
| 468 | * Parse the "Host" header field hostname. | |
| 469 | * | |
| 470 | * @return {String} | |
| 471 | * @api public | |
| 472 | */ | |
| 473 | ||
| 474 | 1 | req.__defineGetter__('host', function(){ |
| 475 | 0 | var trustProxy = this.app.get('trust proxy'); |
| 476 | 0 | var host = trustProxy && this.get('X-Forwarded-Host'); |
| 477 | 0 | host = host || this.get('Host'); |
| 478 | 0 | if (!host) return; |
| 479 | 0 | return host.split(':')[0]; |
| 480 | }); | |
| 481 | ||
| 482 | /** | |
| 483 | * Check if the request is fresh, aka | |
| 484 | * Last-Modified and/or the ETag | |
| 485 | * still match. | |
| 486 | * | |
| 487 | * @return {Boolean} | |
| 488 | * @api public | |
| 489 | */ | |
| 490 | ||
| 491 | 1 | req.__defineGetter__('fresh', function(){ |
| 492 | 0 | var method = this.method; |
| 493 | 0 | var s = this.res.statusCode; |
| 494 | ||
| 495 | // GET or HEAD for weak freshness validation only | |
| 496 | 0 | if ('GET' != method && 'HEAD' != method) return false; |
| 497 | ||
| 498 | // 2xx or 304 as per rfc2616 14.26 | |
| 499 | 0 | if ((s >= 200 && s < 300) || 304 == s) { |
| 500 | 0 | return fresh(this.headers, this.res._headers); |
| 501 | } | |
| 502 | ||
| 503 | 0 | return false; |
| 504 | }); | |
| 505 | ||
| 506 | /** | |
| 507 | * Check if the request is stale, aka | |
| 508 | * "Last-Modified" and / or the "ETag" for the | |
| 509 | * resource has changed. | |
| 510 | * | |
| 511 | * @return {Boolean} | |
| 512 | * @api public | |
| 513 | */ | |
| 514 | ||
| 515 | 1 | req.__defineGetter__('stale', function(){ |
| 516 | 0 | return !this.fresh; |
| 517 | }); | |
| 518 | ||
| 519 | /** | |
| 520 | * Check if the request was an _XMLHttpRequest_. | |
| 521 | * | |
| 522 | * @return {Boolean} | |
| 523 | * @api public | |
| 524 | */ | |
| 525 | ||
| 526 | 1 | req.__defineGetter__('xhr', function(){ |
| 527 | 0 | var val = this.get('X-Requested-With') || ''; |
| 528 | 0 | return 'xmlhttprequest' == val.toLowerCase(); |
| 529 | }); | |
| 530 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var http = require('http') |
| 6 | , path = require('path') | |
| 7 | , connect = require('connect') | |
| 8 | , utils = connect.utils | |
| 9 | , sign = require('cookie-signature').sign | |
| 10 | , normalizeType = require('./utils').normalizeType | |
| 11 | , normalizeTypes = require('./utils').normalizeTypes | |
| 12 | , etag = require('./utils').etag | |
| 13 | , statusCodes = http.STATUS_CODES | |
| 14 | , cookie = require('cookie') | |
| 15 | , send = require('send') | |
| 16 | , mime = connect.mime | |
| 17 | , resolve = require('url').resolve | |
| 18 | , basename = path.basename | |
| 19 | , extname = path.extname; | |
| 20 | ||
| 21 | /** | |
| 22 | * Response prototype. | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | var res = module.exports = { |
| 26 | __proto__: http.ServerResponse.prototype | |
| 27 | }; | |
| 28 | ||
| 29 | /** | |
| 30 | * Set status `code`. | |
| 31 | * | |
| 32 | * @param {Number} code | |
| 33 | * @return {ServerResponse} | |
| 34 | * @api public | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | res.status = function(code){ |
| 38 | 0 | this.statusCode = code; |
| 39 | 0 | return this; |
| 40 | }; | |
| 41 | ||
| 42 | /** | |
| 43 | * Set Link header field with the given `links`. | |
| 44 | * | |
| 45 | * Examples: | |
| 46 | * | |
| 47 | * res.links({ | |
| 48 | * next: 'http://api.example.com/users?page=2', | |
| 49 | * last: 'http://api.example.com/users?page=5' | |
| 50 | * }); | |
| 51 | * | |
| 52 | * @param {Object} links | |
| 53 | * @return {ServerResponse} | |
| 54 | * @api public | |
| 55 | */ | |
| 56 | ||
| 57 | 1 | res.links = function(links){ |
| 58 | 0 | var link = this.get('Link') || ''; |
| 59 | 0 | if (link) link += ', '; |
| 60 | 0 | return this.set('Link', link + Object.keys(links).map(function(rel){ |
| 61 | 0 | return '<' + links[rel] + '>; rel="' + rel + '"'; |
| 62 | }).join(', ')); | |
| 63 | }; | |
| 64 | ||
| 65 | /** | |
| 66 | * Send a response. | |
| 67 | * | |
| 68 | * Examples: | |
| 69 | * | |
| 70 | * res.send(new Buffer('wahoo')); | |
| 71 | * res.send({ some: 'json' }); | |
| 72 | * res.send('<p>some html</p>'); | |
| 73 | * res.send(404, 'Sorry, cant find that'); | |
| 74 | * res.send(404); | |
| 75 | * | |
| 76 | * @param {Mixed} body or status | |
| 77 | * @param {Mixed} body | |
| 78 | * @return {ServerResponse} | |
| 79 | * @api public | |
| 80 | */ | |
| 81 | ||
| 82 | 1 | res.send = function(body){ |
| 83 | 0 | var req = this.req; |
| 84 | 0 | var head = 'HEAD' == req.method; |
| 85 | 0 | var len; |
| 86 | ||
| 87 | // settings | |
| 88 | 0 | var app = this.app; |
| 89 | ||
| 90 | // allow status / body | |
| 91 | 0 | if (2 == arguments.length) { |
| 92 | // res.send(body, status) backwards compat | |
| 93 | 0 | if ('number' != typeof body && 'number' == typeof arguments[1]) { |
| 94 | 0 | this.statusCode = arguments[1]; |
| 95 | } else { | |
| 96 | 0 | this.statusCode = body; |
| 97 | 0 | body = arguments[1]; |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | 0 | switch (typeof body) { |
| 102 | // response status | |
| 103 | case 'number': | |
| 104 | 0 | this.get('Content-Type') || this.type('txt'); |
| 105 | 0 | this.statusCode = body; |
| 106 | 0 | body = http.STATUS_CODES[body]; |
| 107 | 0 | break; |
| 108 | // string defaulting to html | |
| 109 | case 'string': | |
| 110 | 0 | if (!this.get('Content-Type')) { |
| 111 | 0 | this.charset = this.charset || 'utf-8'; |
| 112 | 0 | this.type('html'); |
| 113 | } | |
| 114 | 0 | break; |
| 115 | case 'boolean': | |
| 116 | case 'object': | |
| 117 | 0 | if (null == body) { |
| 118 | 0 | body = ''; |
| 119 | 0 | } else if (Buffer.isBuffer(body)) { |
| 120 | 0 | this.get('Content-Type') || this.type('bin'); |
| 121 | } else { | |
| 122 | 0 | return this.json(body); |
| 123 | } | |
| 124 | 0 | break; |
| 125 | } | |
| 126 | ||
| 127 | // populate Content-Length | |
| 128 | 0 | if (undefined !== body && !this.get('Content-Length')) { |
| 129 | 0 | this.set('Content-Length', len = Buffer.isBuffer(body) |
| 130 | ? body.length | |
| 131 | : Buffer.byteLength(body)); | |
| 132 | } | |
| 133 | ||
| 134 | // ETag support | |
| 135 | // TODO: W/ support | |
| 136 | 0 | if (app.settings.etag && len && 'GET' == req.method) { |
| 137 | 0 | if (!this.get('ETag')) { |
| 138 | 0 | this.set('ETag', etag(body)); |
| 139 | } | |
| 140 | } | |
| 141 | ||
| 142 | // freshness | |
| 143 | 0 | if (req.fresh) this.statusCode = 304; |
| 144 | ||
| 145 | // strip irrelevant headers | |
| 146 | 0 | if (204 == this.statusCode || 304 == this.statusCode) { |
| 147 | 0 | this.removeHeader('Content-Type'); |
| 148 | 0 | this.removeHeader('Content-Length'); |
| 149 | 0 | this.removeHeader('Transfer-Encoding'); |
| 150 | 0 | body = ''; |
| 151 | } | |
| 152 | ||
| 153 | // respond | |
| 154 | 0 | this.end(head ? null : body); |
| 155 | 0 | return this; |
| 156 | }; | |
| 157 | ||
| 158 | /** | |
| 159 | * Send JSON response. | |
| 160 | * | |
| 161 | * Examples: | |
| 162 | * | |
| 163 | * res.json(null); | |
| 164 | * res.json({ user: 'tj' }); | |
| 165 | * res.json(500, 'oh noes!'); | |
| 166 | * res.json(404, 'I dont have that'); | |
| 167 | * | |
| 168 | * @param {Mixed} obj or status | |
| 169 | * @param {Mixed} obj | |
| 170 | * @return {ServerResponse} | |
| 171 | * @api public | |
| 172 | */ | |
| 173 | ||
| 174 | 1 | res.json = function(obj){ |
| 175 | // allow status / body | |
| 176 | 0 | if (2 == arguments.length) { |
| 177 | // res.json(body, status) backwards compat | |
| 178 | 0 | if ('number' == typeof arguments[1]) { |
| 179 | 0 | this.statusCode = arguments[1]; |
| 180 | } else { | |
| 181 | 0 | this.statusCode = obj; |
| 182 | 0 | obj = arguments[1]; |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | // settings | |
| 187 | 0 | var app = this.app; |
| 188 | 0 | var replacer = app.get('json replacer'); |
| 189 | 0 | var spaces = app.get('json spaces'); |
| 190 | 0 | var body = JSON.stringify(obj, replacer, spaces); |
| 191 | ||
| 192 | // content-type | |
| 193 | 0 | this.charset = this.charset || 'utf-8'; |
| 194 | 0 | this.get('Content-Type') || this.set('Content-Type', 'application/json'); |
| 195 | ||
| 196 | 0 | return this.send(body); |
| 197 | }; | |
| 198 | ||
| 199 | /** | |
| 200 | * Send JSON response with JSONP callback support. | |
| 201 | * | |
| 202 | * Examples: | |
| 203 | * | |
| 204 | * res.jsonp(null); | |
| 205 | * res.jsonp({ user: 'tj' }); | |
| 206 | * res.jsonp(500, 'oh noes!'); | |
| 207 | * res.jsonp(404, 'I dont have that'); | |
| 208 | * | |
| 209 | * @param {Mixed} obj or status | |
| 210 | * @param {Mixed} obj | |
| 211 | * @return {ServerResponse} | |
| 212 | * @api public | |
| 213 | */ | |
| 214 | ||
| 215 | 1 | res.jsonp = function(obj){ |
| 216 | // allow status / body | |
| 217 | 0 | if (2 == arguments.length) { |
| 218 | // res.json(body, status) backwards compat | |
| 219 | 0 | if ('number' == typeof arguments[1]) { |
| 220 | 0 | this.statusCode = arguments[1]; |
| 221 | } else { | |
| 222 | 0 | this.statusCode = obj; |
| 223 | 0 | obj = arguments[1]; |
| 224 | } | |
| 225 | } | |
| 226 | ||
| 227 | // settings | |
| 228 | 0 | var app = this.app; |
| 229 | 0 | var replacer = app.get('json replacer'); |
| 230 | 0 | var spaces = app.get('json spaces'); |
| 231 | 0 | var body = JSON.stringify(obj, replacer, spaces) |
| 232 | .replace(/\u2028/g, '\\u2028') | |
| 233 | .replace(/\u2029/g, '\\u2029'); | |
| 234 | 0 | var callback = this.req.query[app.get('jsonp callback name')]; |
| 235 | ||
| 236 | // content-type | |
| 237 | 0 | this.charset = this.charset || 'utf-8'; |
| 238 | 0 | this.set('Content-Type', 'application/json'); |
| 239 | ||
| 240 | // jsonp | |
| 241 | 0 | if (callback) { |
| 242 | 0 | if (Array.isArray(callback)) callback = callback[0]; |
| 243 | 0 | this.set('Content-Type', 'text/javascript'); |
| 244 | 0 | var cb = callback.replace(/[^\[\]\w$.]/g, ''); |
| 245 | 0 | body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');'; |
| 246 | } | |
| 247 | ||
| 248 | 0 | return this.send(body); |
| 249 | }; | |
| 250 | ||
| 251 | /** | |
| 252 | * Transfer the file at the given `path`. | |
| 253 | * | |
| 254 | * Automatically sets the _Content-Type_ response header field. | |
| 255 | * The callback `fn(err)` is invoked when the transfer is complete | |
| 256 | * or when an error occurs. Be sure to check `res.sentHeader` | |
| 257 | * if you wish to attempt responding, as the header and some data | |
| 258 | * may have already been transferred. | |
| 259 | * | |
| 260 | * Options: | |
| 261 | * | |
| 262 | * - `maxAge` defaulting to 0 | |
| 263 | * - `root` root directory for relative filenames | |
| 264 | * | |
| 265 | * Examples: | |
| 266 | * | |
| 267 | * The following example illustrates how `res.sendfile()` may | |
| 268 | * be used as an alternative for the `static()` middleware for | |
| 269 | * dynamic situations. The code backing `res.sendfile()` is actually | |
| 270 | * the same code, so HTTP cache support etc is identical. | |
| 271 | * | |
| 272 | * app.get('/user/:uid/photos/:file', function(req, res){ | |
| 273 | * var uid = req.params.uid | |
| 274 | * , file = req.params.file; | |
| 275 | * | |
| 276 | * req.user.mayViewFilesFrom(uid, function(yes){ | |
| 277 | * if (yes) { | |
| 278 | * res.sendfile('/uploads/' + uid + '/' + file); | |
| 279 | * } else { | |
| 280 | * res.send(403, 'Sorry! you cant see that.'); | |
| 281 | * } | |
| 282 | * }); | |
| 283 | * }); | |
| 284 | * | |
| 285 | * @param {String} path | |
| 286 | * @param {Object|Function} options or fn | |
| 287 | * @param {Function} fn | |
| 288 | * @api public | |
| 289 | */ | |
| 290 | ||
| 291 | 1 | res.sendfile = function(path, options, fn){ |
| 292 | 0 | var self = this |
| 293 | , req = self.req | |
| 294 | , next = this.req.next | |
| 295 | , options = options || {} | |
| 296 | , done; | |
| 297 | ||
| 298 | // support function as second arg | |
| 299 | 0 | if ('function' == typeof options) { |
| 300 | 0 | fn = options; |
| 301 | 0 | options = {}; |
| 302 | } | |
| 303 | ||
| 304 | // socket errors | |
| 305 | 0 | req.socket.on('error', error); |
| 306 | ||
| 307 | // errors | |
| 308 | 0 | function error(err) { |
| 309 | 0 | if (done) return; |
| 310 | 0 | done = true; |
| 311 | ||
| 312 | // clean up | |
| 313 | 0 | cleanup(); |
| 314 | 0 | if (!self.headerSent) self.removeHeader('Content-Disposition'); |
| 315 | ||
| 316 | // callback available | |
| 317 | 0 | if (fn) return fn(err); |
| 318 | ||
| 319 | // list in limbo if there's no callback | |
| 320 | 0 | if (self.headerSent) return; |
| 321 | ||
| 322 | // delegate | |
| 323 | 0 | next(err); |
| 324 | } | |
| 325 | ||
| 326 | // streaming | |
| 327 | 0 | function stream(stream) { |
| 328 | 0 | if (done) return; |
| 329 | 0 | cleanup(); |
| 330 | 0 | if (fn) stream.on('end', fn); |
| 331 | } | |
| 332 | ||
| 333 | // cleanup | |
| 334 | 0 | function cleanup() { |
| 335 | 0 | req.socket.removeListener('error', error); |
| 336 | } | |
| 337 | ||
| 338 | // transfer | |
| 339 | 0 | var file = send(req, path); |
| 340 | 0 | if (options.root) file.root(options.root); |
| 341 | 0 | file.maxage(options.maxAge || 0); |
| 342 | 0 | file.on('error', error); |
| 343 | 0 | file.on('directory', next); |
| 344 | 0 | file.on('stream', stream); |
| 345 | 0 | file.pipe(this); |
| 346 | 0 | this.on('finish', cleanup); |
| 347 | }; | |
| 348 | ||
| 349 | /** | |
| 350 | * Transfer the file at the given `path` as an attachment. | |
| 351 | * | |
| 352 | * Optionally providing an alternate attachment `filename`, | |
| 353 | * and optional callback `fn(err)`. The callback is invoked | |
| 354 | * when the data transfer is complete, or when an error has | |
| 355 | * ocurred. Be sure to check `res.headerSent` if you plan to respond. | |
| 356 | * | |
| 357 | * This method uses `res.sendfile()`. | |
| 358 | * | |
| 359 | * @param {String} path | |
| 360 | * @param {String|Function} filename or fn | |
| 361 | * @param {Function} fn | |
| 362 | * @api public | |
| 363 | */ | |
| 364 | ||
| 365 | 1 | res.download = function(path, filename, fn){ |
| 366 | // support function as second arg | |
| 367 | 0 | if ('function' == typeof filename) { |
| 368 | 0 | fn = filename; |
| 369 | 0 | filename = null; |
| 370 | } | |
| 371 | ||
| 372 | 0 | filename = filename || path; |
| 373 | 0 | this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"'); |
| 374 | 0 | return this.sendfile(path, fn); |
| 375 | }; | |
| 376 | ||
| 377 | /** | |
| 378 | * Set _Content-Type_ response header with `type` through `mime.lookup()` | |
| 379 | * when it does not contain "/", or set the Content-Type to `type` otherwise. | |
| 380 | * | |
| 381 | * Examples: | |
| 382 | * | |
| 383 | * res.type('.html'); | |
| 384 | * res.type('html'); | |
| 385 | * res.type('json'); | |
| 386 | * res.type('application/json'); | |
| 387 | * res.type('png'); | |
| 388 | * | |
| 389 | * @param {String} type | |
| 390 | * @return {ServerResponse} for chaining | |
| 391 | * @api public | |
| 392 | */ | |
| 393 | ||
| 394 | 1 | res.contentType = |
| 395 | res.type = function(type){ | |
| 396 | 0 | return this.set('Content-Type', ~type.indexOf('/') |
| 397 | ? type | |
| 398 | : mime.lookup(type)); | |
| 399 | }; | |
| 400 | ||
| 401 | /** | |
| 402 | * Respond to the Acceptable formats using an `obj` | |
| 403 | * of mime-type callbacks. | |
| 404 | * | |
| 405 | * This method uses `req.accepted`, an array of | |
| 406 | * acceptable types ordered by their quality values. | |
| 407 | * When "Accept" is not present the _first_ callback | |
| 408 | * is invoked, otherwise the first match is used. When | |
| 409 | * no match is performed the server responds with | |
| 410 | * 406 "Not Acceptable". | |
| 411 | * | |
| 412 | * Content-Type is set for you, however if you choose | |
| 413 | * you may alter this within the callback using `res.type()` | |
| 414 | * or `res.set('Content-Type', ...)`. | |
| 415 | * | |
| 416 | * res.format({ | |
| 417 | * 'text/plain': function(){ | |
| 418 | * res.send('hey'); | |
| 419 | * }, | |
| 420 | * | |
| 421 | * 'text/html': function(){ | |
| 422 | * res.send('<p>hey</p>'); | |
| 423 | * }, | |
| 424 | * | |
| 425 | * 'appliation/json': function(){ | |
| 426 | * res.send({ message: 'hey' }); | |
| 427 | * } | |
| 428 | * }); | |
| 429 | * | |
| 430 | * In addition to canonicalized MIME types you may | |
| 431 | * also use extnames mapped to these types: | |
| 432 | * | |
| 433 | * res.format({ | |
| 434 | * text: function(){ | |
| 435 | * res.send('hey'); | |
| 436 | * }, | |
| 437 | * | |
| 438 | * html: function(){ | |
| 439 | * res.send('<p>hey</p>'); | |
| 440 | * }, | |
| 441 | * | |
| 442 | * json: function(){ | |
| 443 | * res.send({ message: 'hey' }); | |
| 444 | * } | |
| 445 | * }); | |
| 446 | * | |
| 447 | * By default Express passes an `Error` | |
| 448 | * with a `.status` of 406 to `next(err)` | |
| 449 | * if a match is not made. If you provide | |
| 450 | * a `.default` callback it will be invoked | |
| 451 | * instead. | |
| 452 | * | |
| 453 | * @param {Object} obj | |
| 454 | * @return {ServerResponse} for chaining | |
| 455 | * @api public | |
| 456 | */ | |
| 457 | ||
| 458 | 1 | res.format = function(obj){ |
| 459 | 0 | var req = this.req |
| 460 | , next = req.next; | |
| 461 | ||
| 462 | 0 | var fn = obj.default; |
| 463 | 0 | if (fn) delete obj.default; |
| 464 | 0 | var keys = Object.keys(obj); |
| 465 | ||
| 466 | 0 | var key = req.accepts(keys); |
| 467 | ||
| 468 | 0 | this.vary("Accept"); |
| 469 | ||
| 470 | 0 | if (key) { |
| 471 | 0 | var type = normalizeType(key).value; |
| 472 | 0 | var charset = mime.charsets.lookup(type); |
| 473 | 0 | if (charset) type += '; charset=' + charset; |
| 474 | 0 | this.set('Content-Type', type); |
| 475 | 0 | obj[key](req, this, next); |
| 476 | 0 | } else if (fn) { |
| 477 | 0 | fn(); |
| 478 | } else { | |
| 479 | 0 | var err = new Error('Not Acceptable'); |
| 480 | 0 | err.status = 406; |
| 481 | 0 | err.types = normalizeTypes(keys).map(function(o){ return o.value }); |
| 482 | 0 | next(err); |
| 483 | } | |
| 484 | ||
| 485 | 0 | return this; |
| 486 | }; | |
| 487 | ||
| 488 | /** | |
| 489 | * Set _Content-Disposition_ header to _attachment_ with optional `filename`. | |
| 490 | * | |
| 491 | * @param {String} filename | |
| 492 | * @return {ServerResponse} | |
| 493 | * @api public | |
| 494 | */ | |
| 495 | ||
| 496 | 1 | res.attachment = function(filename){ |
| 497 | 0 | if (filename) this.type(extname(filename)); |
| 498 | 0 | this.set('Content-Disposition', filename |
| 499 | ? 'attachment; filename="' + basename(filename) + '"' | |
| 500 | : 'attachment'); | |
| 501 | 0 | return this; |
| 502 | }; | |
| 503 | ||
| 504 | /** | |
| 505 | * Set header `field` to `val`, or pass | |
| 506 | * an object of header fields. | |
| 507 | * | |
| 508 | * Examples: | |
| 509 | * | |
| 510 | * res.set('Foo', ['bar', 'baz']); | |
| 511 | * res.set('Accept', 'application/json'); | |
| 512 | * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); | |
| 513 | * | |
| 514 | * Aliased as `res.header()`. | |
| 515 | * | |
| 516 | * @param {String|Object|Array} field | |
| 517 | * @param {String} val | |
| 518 | * @return {ServerResponse} for chaining | |
| 519 | * @api public | |
| 520 | */ | |
| 521 | ||
| 522 | 1 | res.set = |
| 523 | res.header = function(field, val){ | |
| 524 | 0 | if (2 == arguments.length) { |
| 525 | 0 | if (Array.isArray(val)) val = val.map(String); |
| 526 | 0 | else val = String(val); |
| 527 | 0 | this.setHeader(field, val); |
| 528 | } else { | |
| 529 | 0 | for (var key in field) { |
| 530 | 0 | this.set(key, field[key]); |
| 531 | } | |
| 532 | } | |
| 533 | 0 | return this; |
| 534 | }; | |
| 535 | ||
| 536 | /** | |
| 537 | * Get value for header `field`. | |
| 538 | * | |
| 539 | * @param {String} field | |
| 540 | * @return {String} | |
| 541 | * @api public | |
| 542 | */ | |
| 543 | ||
| 544 | 1 | res.get = function(field){ |
| 545 | 0 | return this.getHeader(field); |
| 546 | }; | |
| 547 | ||
| 548 | /** | |
| 549 | * Clear cookie `name`. | |
| 550 | * | |
| 551 | * @param {String} name | |
| 552 | * @param {Object} options | |
| 553 | * @param {ServerResponse} for chaining | |
| 554 | * @api public | |
| 555 | */ | |
| 556 | ||
| 557 | 1 | res.clearCookie = function(name, options){ |
| 558 | 0 | var opts = { expires: new Date(1), path: '/' }; |
| 559 | 0 | return this.cookie(name, '', options |
| 560 | ? utils.merge(opts, options) | |
| 561 | : opts); | |
| 562 | }; | |
| 563 | ||
| 564 | /** | |
| 565 | * Set cookie `name` to `val`, with the given `options`. | |
| 566 | * | |
| 567 | * Options: | |
| 568 | * | |
| 569 | * - `maxAge` max-age in milliseconds, converted to `expires` | |
| 570 | * - `signed` sign the cookie | |
| 571 | * - `path` defaults to "/" | |
| 572 | * | |
| 573 | * Examples: | |
| 574 | * | |
| 575 | * // "Remember Me" for 15 minutes | |
| 576 | * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); | |
| 577 | * | |
| 578 | * // save as above | |
| 579 | * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) | |
| 580 | * | |
| 581 | * @param {String} name | |
| 582 | * @param {String|Object} val | |
| 583 | * @param {Options} options | |
| 584 | * @api public | |
| 585 | */ | |
| 586 | ||
| 587 | 1 | res.cookie = function(name, val, options){ |
| 588 | 0 | options = utils.merge({}, options); |
| 589 | 0 | var secret = this.req.secret; |
| 590 | 0 | var signed = options.signed; |
| 591 | 0 | if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies'); |
| 592 | 0 | if ('number' == typeof val) val = val.toString(); |
| 593 | 0 | if ('object' == typeof val) val = 'j:' + JSON.stringify(val); |
| 594 | 0 | if (signed) val = 's:' + sign(val, secret); |
| 595 | 0 | if ('maxAge' in options) { |
| 596 | 0 | options.expires = new Date(Date.now() + options.maxAge); |
| 597 | 0 | options.maxAge /= 1000; |
| 598 | } | |
| 599 | 0 | if (null == options.path) options.path = '/'; |
| 600 | 0 | this.set('Set-Cookie', cookie.serialize(name, String(val), options)); |
| 601 | 0 | return this; |
| 602 | }; | |
| 603 | ||
| 604 | ||
| 605 | /** | |
| 606 | * Set the location header to `url`. | |
| 607 | * | |
| 608 | * The given `url` can also be "back", which redirects | |
| 609 | * to the _Referrer_ or _Referer_ headers or "/". | |
| 610 | * | |
| 611 | * Examples: | |
| 612 | * | |
| 613 | * res.location('/foo/bar').; | |
| 614 | * res.location('http://example.com'); | |
| 615 | * res.location('../login'); // /blog/post/1 -> /blog/login | |
| 616 | * | |
| 617 | * Mounting: | |
| 618 | * | |
| 619 | * When an application is mounted and `res.location()` | |
| 620 | * is given a path that does _not_ lead with "/" it becomes | |
| 621 | * relative to the mount-point. For example if the application | |
| 622 | * is mounted at "/blog", the following would become "/blog/login". | |
| 623 | * | |
| 624 | * res.location('login'); | |
| 625 | * | |
| 626 | * While the leading slash would result in a location of "/login": | |
| 627 | * | |
| 628 | * res.location('/login'); | |
| 629 | * | |
| 630 | * @param {String} url | |
| 631 | * @api public | |
| 632 | */ | |
| 633 | ||
| 634 | 1 | res.location = function(url){ |
| 635 | 0 | var app = this.app |
| 636 | , req = this.req | |
| 637 | , path; | |
| 638 | ||
| 639 | // "back" is an alias for the referrer | |
| 640 | 0 | if ('back' == url) url = req.get('Referrer') || '/'; |
| 641 | ||
| 642 | // relative | |
| 643 | 0 | if (!~url.indexOf('://') && 0 != url.indexOf('//')) { |
| 644 | // relative to path | |
| 645 | 0 | if ('.' == url[0]) { |
| 646 | 0 | path = req.originalUrl.split('?')[0]; |
| 647 | 0 | path = path + ('/' == path[path.length - 1] ? '' : '/'); |
| 648 | 0 | url = resolve(path, url); |
| 649 | // relative to mount-point | |
| 650 | 0 | } else if ('/' != url[0]) { |
| 651 | 0 | path = app.path(); |
| 652 | 0 | url = path + '/' + url; |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | // Respond | |
| 657 | 0 | this.set('Location', url); |
| 658 | 0 | return this; |
| 659 | }; | |
| 660 | ||
| 661 | /** | |
| 662 | * Redirect to the given `url` with optional response `status` | |
| 663 | * defaulting to 302. | |
| 664 | * | |
| 665 | * The resulting `url` is determined by `res.location()`, so | |
| 666 | * it will play nicely with mounted apps, relative paths, | |
| 667 | * `"back"` etc. | |
| 668 | * | |
| 669 | * Examples: | |
| 670 | * | |
| 671 | * res.redirect('/foo/bar'); | |
| 672 | * res.redirect('http://example.com'); | |
| 673 | * res.redirect(301, 'http://example.com'); | |
| 674 | * res.redirect('http://example.com', 301); | |
| 675 | * res.redirect('../login'); // /blog/post/1 -> /blog/login | |
| 676 | * | |
| 677 | * @param {String} url | |
| 678 | * @param {Number} code | |
| 679 | * @api public | |
| 680 | */ | |
| 681 | ||
| 682 | 1 | res.redirect = function(url){ |
| 683 | 0 | var head = 'HEAD' == this.req.method |
| 684 | , status = 302 | |
| 685 | , body; | |
| 686 | ||
| 687 | // allow status / url | |
| 688 | 0 | if (2 == arguments.length) { |
| 689 | 0 | if ('number' == typeof url) { |
| 690 | 0 | status = url; |
| 691 | 0 | url = arguments[1]; |
| 692 | } else { | |
| 693 | 0 | status = arguments[1]; |
| 694 | } | |
| 695 | } | |
| 696 | ||
| 697 | // Set location header | |
| 698 | 0 | this.location(url); |
| 699 | 0 | url = this.get('Location'); |
| 700 | ||
| 701 | // Support text/{plain,html} by default | |
| 702 | 0 | this.format({ |
| 703 | text: function(){ | |
| 704 | 0 | body = statusCodes[status] + '. Redirecting to ' + encodeURI(url); |
| 705 | }, | |
| 706 | ||
| 707 | html: function(){ | |
| 708 | 0 | var u = utils.escape(url); |
| 709 | 0 | body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'; |
| 710 | }, | |
| 711 | ||
| 712 | default: function(){ | |
| 713 | 0 | body = ''; |
| 714 | } | |
| 715 | }); | |
| 716 | ||
| 717 | // Respond | |
| 718 | 0 | this.statusCode = status; |
| 719 | 0 | this.set('Content-Length', Buffer.byteLength(body)); |
| 720 | 0 | this.end(head ? null : body); |
| 721 | }; | |
| 722 | ||
| 723 | /** | |
| 724 | * Add `field` to Vary. If already present in the Vary set, then | |
| 725 | * this call is simply ignored. | |
| 726 | * | |
| 727 | * @param {Array|String} field | |
| 728 | * @param {ServerResponse} for chaining | |
| 729 | * @api public | |
| 730 | */ | |
| 731 | ||
| 732 | 1 | res.vary = function(field){ |
| 733 | 0 | var self = this; |
| 734 | ||
| 735 | // nothing | |
| 736 | 0 | if (!field) return this; |
| 737 | ||
| 738 | // array | |
| 739 | 0 | if (Array.isArray(field)) { |
| 740 | 0 | field.forEach(function(field){ |
| 741 | 0 | self.vary(field); |
| 742 | }); | |
| 743 | 0 | return; |
| 744 | } | |
| 745 | ||
| 746 | 0 | var vary = this.get('Vary'); |
| 747 | ||
| 748 | // append | |
| 749 | 0 | if (vary) { |
| 750 | 0 | vary = vary.split(/ *, */); |
| 751 | 0 | if (!~vary.indexOf(field)) vary.push(field); |
| 752 | 0 | this.set('Vary', vary.join(', ')); |
| 753 | 0 | return this; |
| 754 | } | |
| 755 | ||
| 756 | // set | |
| 757 | 0 | this.set('Vary', field); |
| 758 | 0 | return this; |
| 759 | }; | |
| 760 | ||
| 761 | /** | |
| 762 | * Render `view` with the given `options` and optional callback `fn`. | |
| 763 | * When a callback function is given a response will _not_ be made | |
| 764 | * automatically, otherwise a response of _200_ and _text/html_ is given. | |
| 765 | * | |
| 766 | * Options: | |
| 767 | * | |
| 768 | * - `cache` boolean hinting to the engine it should cache | |
| 769 | * - `filename` filename of the view being rendered | |
| 770 | * | |
| 771 | * @param {String} view | |
| 772 | * @param {Object|Function} options or callback function | |
| 773 | * @param {Function} fn | |
| 774 | * @api public | |
| 775 | */ | |
| 776 | ||
| 777 | 1 | res.render = function(view, options, fn){ |
| 778 | 0 | var self = this |
| 779 | , options = options || {} | |
| 780 | , req = this.req | |
| 781 | , app = req.app; | |
| 782 | ||
| 783 | // support callback function as second arg | |
| 784 | 0 | if ('function' == typeof options) { |
| 785 | 0 | fn = options, options = {}; |
| 786 | } | |
| 787 | ||
| 788 | // merge res.locals | |
| 789 | 0 | options._locals = self.locals; |
| 790 | ||
| 791 | // default callback to respond | |
| 792 | 0 | fn = fn || function(err, str){ |
| 793 | 0 | if (err) return req.next(err); |
| 794 | 0 | self.send(str); |
| 795 | }; | |
| 796 | ||
| 797 | // render | |
| 798 | 0 | app.render(view, options, fn); |
| 799 | }; | |
| 800 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Route = require('./route') |
| 6 | , utils = require('../utils') | |
| 7 | , methods = require('methods') | |
| 8 | , debug = require('debug')('express:router') | |
| 9 | , parse = require('connect').utils.parseUrl; | |
| 10 | ||
| 11 | /** | |
| 12 | * Expose `Router` constructor. | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | exports = module.exports = Router; |
| 16 | ||
| 17 | /** | |
| 18 | * Initialize a new `Router` with the given `options`. | |
| 19 | * | |
| 20 | * @param {Object} options | |
| 21 | * @api private | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | function Router(options) { |
| 25 | 1 | options = options || {}; |
| 26 | 1 | var self = this; |
| 27 | 1 | this.map = {}; |
| 28 | 1 | this.params = {}; |
| 29 | 1 | this._params = []; |
| 30 | 1 | this.caseSensitive = options.caseSensitive; |
| 31 | 1 | this.strict = options.strict; |
| 32 | 1 | this.middleware = function router(req, res, next){ |
| 33 | 0 | self._dispatch(req, res, next); |
| 34 | }; | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Register a param callback `fn` for the given `name`. | |
| 39 | * | |
| 40 | * @param {String|Function} name | |
| 41 | * @param {Function} fn | |
| 42 | * @return {Router} for chaining | |
| 43 | * @api public | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | Router.prototype.param = function(name, fn){ |
| 47 | // param logic | |
| 48 | 0 | if ('function' == typeof name) { |
| 49 | 0 | this._params.push(name); |
| 50 | 0 | return; |
| 51 | } | |
| 52 | ||
| 53 | // apply param functions | |
| 54 | 0 | var params = this._params |
| 55 | , len = params.length | |
| 56 | , ret; | |
| 57 | ||
| 58 | 0 | for (var i = 0; i < len; ++i) { |
| 59 | 0 | if (ret = params[i](name, fn)) { |
| 60 | 0 | fn = ret; |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | // ensure we end up with a | |
| 65 | // middleware function | |
| 66 | 0 | if ('function' != typeof fn) { |
| 67 | 0 | throw new Error('invalid param() call for ' + name + ', got ' + fn); |
| 68 | } | |
| 69 | ||
| 70 | 0 | (this.params[name] = this.params[name] || []).push(fn); |
| 71 | 0 | return this; |
| 72 | }; | |
| 73 | ||
| 74 | /** | |
| 75 | * Route dispatcher aka the route "middleware". | |
| 76 | * | |
| 77 | * @param {IncomingMessage} req | |
| 78 | * @param {ServerResponse} res | |
| 79 | * @param {Function} next | |
| 80 | * @api private | |
| 81 | */ | |
| 82 | ||
| 83 | 1 | Router.prototype._dispatch = function(req, res, next){ |
| 84 | 0 | var params = this.params |
| 85 | , self = this; | |
| 86 | ||
| 87 | 0 | debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl); |
| 88 | ||
| 89 | // route dispatch | |
| 90 | 0 | (function pass(i, err){ |
| 91 | 0 | var paramCallbacks |
| 92 | , paramIndex = 0 | |
| 93 | , paramVal | |
| 94 | , route | |
| 95 | , keys | |
| 96 | , key; | |
| 97 | ||
| 98 | // match next route | |
| 99 | 0 | function nextRoute(err) { |
| 100 | 0 | pass(req._route_index + 1, err); |
| 101 | } | |
| 102 | ||
| 103 | // match route | |
| 104 | 0 | req.route = route = self.matchRequest(req, i); |
| 105 | ||
| 106 | // implied OPTIONS | |
| 107 | 0 | if (!route && 'OPTIONS' == req.method) return self._options(req, res, next); |
| 108 | ||
| 109 | // no route | |
| 110 | 0 | if (!route) return next(err); |
| 111 | 0 | debug('matched %s %s', route.method, route.path); |
| 112 | ||
| 113 | // we have a route | |
| 114 | // start at param 0 | |
| 115 | 0 | req.params = route.params; |
| 116 | 0 | keys = route.keys; |
| 117 | 0 | i = 0; |
| 118 | ||
| 119 | // param callbacks | |
| 120 | 0 | function param(err) { |
| 121 | 0 | paramIndex = 0; |
| 122 | 0 | key = keys[i++]; |
| 123 | 0 | paramVal = key && req.params[key.name]; |
| 124 | 0 | paramCallbacks = key && params[key.name]; |
| 125 | ||
| 126 | 0 | try { |
| 127 | 0 | if ('route' == err) { |
| 128 | 0 | nextRoute(); |
| 129 | 0 | } else if (err) { |
| 130 | 0 | i = 0; |
| 131 | 0 | callbacks(err); |
| 132 | 0 | } else if (paramCallbacks && undefined !== paramVal) { |
| 133 | 0 | paramCallback(); |
| 134 | 0 | } else if (key) { |
| 135 | 0 | param(); |
| 136 | } else { | |
| 137 | 0 | i = 0; |
| 138 | 0 | callbacks(); |
| 139 | } | |
| 140 | } catch (err) { | |
| 141 | 0 | param(err); |
| 142 | } | |
| 143 | }; | |
| 144 | ||
| 145 | 0 | param(err); |
| 146 | ||
| 147 | // single param callbacks | |
| 148 | 0 | function paramCallback(err) { |
| 149 | 0 | var fn = paramCallbacks[paramIndex++]; |
| 150 | 0 | if (err || !fn) return param(err); |
| 151 | 0 | fn(req, res, paramCallback, paramVal, key.name); |
| 152 | } | |
| 153 | ||
| 154 | // invoke route callbacks | |
| 155 | 0 | function callbacks(err) { |
| 156 | 0 | var fn = route.callbacks[i++]; |
| 157 | 0 | try { |
| 158 | 0 | if ('route' == err) { |
| 159 | 0 | nextRoute(); |
| 160 | 0 | } else if (err && fn) { |
| 161 | 0 | if (fn.length < 4) return callbacks(err); |
| 162 | 0 | fn(err, req, res, callbacks); |
| 163 | 0 | } else if (fn) { |
| 164 | 0 | if (fn.length < 4) return fn(req, res, callbacks); |
| 165 | 0 | callbacks(); |
| 166 | } else { | |
| 167 | 0 | nextRoute(err); |
| 168 | } | |
| 169 | } catch (err) { | |
| 170 | 0 | callbacks(err); |
| 171 | } | |
| 172 | } | |
| 173 | })(0); | |
| 174 | }; | |
| 175 | ||
| 176 | /** | |
| 177 | * Respond to __OPTIONS__ method. | |
| 178 | * | |
| 179 | * @param {IncomingMessage} req | |
| 180 | * @param {ServerResponse} res | |
| 181 | * @api private | |
| 182 | */ | |
| 183 | ||
| 184 | 1 | Router.prototype._options = function(req, res, next){ |
| 185 | 0 | var path = parse(req).pathname |
| 186 | , body = this._optionsFor(path).join(','); | |
| 187 | 0 | if (!body) return next(); |
| 188 | 0 | res.set('Allow', body).send(body); |
| 189 | }; | |
| 190 | ||
| 191 | /** | |
| 192 | * Return an array of HTTP verbs or "options" for `path`. | |
| 193 | * | |
| 194 | * @param {String} path | |
| 195 | * @return {Array} | |
| 196 | * @api private | |
| 197 | */ | |
| 198 | ||
| 199 | 1 | Router.prototype._optionsFor = function(path){ |
| 200 | 0 | var self = this; |
| 201 | 0 | return methods.filter(function(method){ |
| 202 | 0 | var routes = self.map[method]; |
| 203 | 0 | if (!routes || 'options' == method) return; |
| 204 | 0 | for (var i = 0, len = routes.length; i < len; ++i) { |
| 205 | 0 | if (routes[i].match(path)) return true; |
| 206 | } | |
| 207 | }).map(function(method){ | |
| 208 | 0 | return method.toUpperCase(); |
| 209 | }); | |
| 210 | }; | |
| 211 | ||
| 212 | /** | |
| 213 | * Attempt to match a route for `req` | |
| 214 | * with optional starting index of `i` | |
| 215 | * defaulting to 0. | |
| 216 | * | |
| 217 | * @param {IncomingMessage} req | |
| 218 | * @param {Number} i | |
| 219 | * @return {Route} | |
| 220 | * @api private | |
| 221 | */ | |
| 222 | ||
| 223 | 1 | Router.prototype.matchRequest = function(req, i, head){ |
| 224 | 0 | var method = req.method.toLowerCase() |
| 225 | , url = parse(req) | |
| 226 | , path = url.pathname | |
| 227 | , routes = this.map | |
| 228 | , i = i || 0 | |
| 229 | , route; | |
| 230 | ||
| 231 | // HEAD support | |
| 232 | 0 | if (!head && 'head' == method) { |
| 233 | 0 | route = this.matchRequest(req, i, true); |
| 234 | 0 | if (route) return route; |
| 235 | 0 | method = 'get'; |
| 236 | } | |
| 237 | ||
| 238 | // routes for this method | |
| 239 | 0 | if (routes = routes[method]) { |
| 240 | ||
| 241 | // matching routes | |
| 242 | 0 | for (var len = routes.length; i < len; ++i) { |
| 243 | 0 | route = routes[i]; |
| 244 | 0 | if (route.match(path)) { |
| 245 | 0 | req._route_index = i; |
| 246 | 0 | return route; |
| 247 | } | |
| 248 | } | |
| 249 | } | |
| 250 | }; | |
| 251 | ||
| 252 | /** | |
| 253 | * Attempt to match a route for `method` | |
| 254 | * and `url` with optional starting | |
| 255 | * index of `i` defaulting to 0. | |
| 256 | * | |
| 257 | * @param {String} method | |
| 258 | * @param {String} url | |
| 259 | * @param {Number} i | |
| 260 | * @return {Route} | |
| 261 | * @api private | |
| 262 | */ | |
| 263 | ||
| 264 | 1 | Router.prototype.match = function(method, url, i, head){ |
| 265 | 0 | var req = { method: method, url: url }; |
| 266 | 0 | return this.matchRequest(req, i, head); |
| 267 | }; | |
| 268 | ||
| 269 | /** | |
| 270 | * Route `method`, `path`, and one or more callbacks. | |
| 271 | * | |
| 272 | * @param {String} method | |
| 273 | * @param {String} path | |
| 274 | * @param {Function} callback... | |
| 275 | * @return {Router} for chaining | |
| 276 | * @api private | |
| 277 | */ | |
| 278 | ||
| 279 | 1 | Router.prototype.route = function(method, path, callbacks){ |
| 280 | 0 | var method = method.toLowerCase() |
| 281 | , callbacks = utils.flatten([].slice.call(arguments, 2)); | |
| 282 | ||
| 283 | // ensure path was given | |
| 284 | 0 | if (!path) throw new Error('Router#' + method + '() requires a path'); |
| 285 | ||
| 286 | // ensure all callbacks are functions | |
| 287 | 0 | callbacks.forEach(function(fn){ |
| 288 | 0 | if ('function' == typeof fn) return; |
| 289 | 0 | var type = {}.toString.call(fn); |
| 290 | 0 | var msg = '.' + method + '() requires callback functions but got a ' + type; |
| 291 | 0 | throw new Error(msg); |
| 292 | }); | |
| 293 | ||
| 294 | // create the route | |
| 295 | 0 | debug('defined %s %s', method, path); |
| 296 | 0 | var route = new Route(method, path, callbacks, { |
| 297 | sensitive: this.caseSensitive, | |
| 298 | strict: this.strict | |
| 299 | }); | |
| 300 | ||
| 301 | // add it | |
| 302 | 0 | (this.map[method] = this.map[method] || []).push(route); |
| 303 | 0 | return this; |
| 304 | }; | |
| 305 | ||
| 306 | 1 | Router.prototype.all = function(path) { |
| 307 | 0 | var self = this; |
| 308 | 0 | var args = [].slice.call(arguments); |
| 309 | 0 | methods.forEach(function(method){ |
| 310 | 0 | self.route.apply(self, [method].concat(args)); |
| 311 | }); | |
| 312 | 0 | return this; |
| 313 | }; | |
| 314 | ||
| 315 | 1 | methods.forEach(function(method){ |
| 316 | 24 | Router.prototype[method] = function(path){ |
| 317 | 0 | var args = [method].concat([].slice.call(arguments)); |
| 318 | 0 | this.route.apply(this, args); |
| 319 | 0 | return this; |
| 320 | }; | |
| 321 | }); | |
| 322 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('../utils'); |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Route`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | module.exports = Route; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize `Route` with the given HTTP `method`, `path`, | |
| 16 | * and an array of `callbacks` and `options`. | |
| 17 | * | |
| 18 | * Options: | |
| 19 | * | |
| 20 | * - `sensitive` enable case-sensitive routes | |
| 21 | * - `strict` enable strict matching for trailing slashes | |
| 22 | * | |
| 23 | * @param {String} method | |
| 24 | * @param {String} path | |
| 25 | * @param {Array} callbacks | |
| 26 | * @param {Object} options. | |
| 27 | * @api private | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | function Route(method, path, callbacks, options) { |
| 31 | 0 | options = options || {}; |
| 32 | 0 | this.path = path; |
| 33 | 0 | this.method = method; |
| 34 | 0 | this.callbacks = callbacks; |
| 35 | 0 | this.regexp = utils.pathRegexp(path |
| 36 | , this.keys = [] | |
| 37 | , options.sensitive | |
| 38 | , options.strict); | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Check if this route matches `path`, if so | |
| 43 | * populate `.params`. | |
| 44 | * | |
| 45 | * @param {String} path | |
| 46 | * @return {Boolean} | |
| 47 | * @api private | |
| 48 | */ | |
| 49 | ||
| 50 | 1 | Route.prototype.match = function(path){ |
| 51 | 0 | var keys = this.keys |
| 52 | , params = this.params = [] | |
| 53 | , m = this.regexp.exec(path); | |
| 54 | ||
| 55 | 0 | if (!m) return false; |
| 56 | ||
| 57 | 0 | for (var i = 1, len = m.length; i < len; ++i) { |
| 58 | 0 | var key = keys[i - 1]; |
| 59 | ||
| 60 | 0 | try { |
| 61 | 0 | var val = 'string' == typeof m[i] |
| 62 | ? decodeURIComponent(m[i]) | |
| 63 | : m[i]; | |
| 64 | } catch(e) { | |
| 65 | 0 | var err = new Error("Failed to decode param '" + m[i] + "'"); |
| 66 | 0 | err.status = 400; |
| 67 | 0 | throw err; |
| 68 | } | |
| 69 | ||
| 70 | 0 | if (key) { |
| 71 | 0 | params[key.name] = val; |
| 72 | } else { | |
| 73 | 0 | params.push(val); |
| 74 | } | |
| 75 | } | |
| 76 | ||
| 77 | 0 | return true; |
| 78 | }; | |
| 79 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var mime = require('connect').mime |
| 7 | , crc32 = require('buffer-crc32'); | |
| 8 | ||
| 9 | /** | |
| 10 | * toString ref. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | var toString = {}.toString; |
| 14 | ||
| 15 | /** | |
| 16 | * Return ETag for `body`. | |
| 17 | * | |
| 18 | * @param {String|Buffer} body | |
| 19 | * @return {String} | |
| 20 | * @api private | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | exports.etag = function(body){ |
| 24 | 0 | return '"' + crc32.signed(body) + '"'; |
| 25 | }; | |
| 26 | ||
| 27 | /** | |
| 28 | * Make `locals()` bound to the given `obj`. | |
| 29 | * | |
| 30 | * This is used for `app.locals` and `res.locals`. | |
| 31 | * | |
| 32 | * @param {Object} obj | |
| 33 | * @return {Function} | |
| 34 | * @api private | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | exports.locals = function(){ |
| 38 | 1 | function locals(obj){ |
| 39 | 0 | for (var key in obj) locals[key] = obj[key]; |
| 40 | 0 | return obj; |
| 41 | }; | |
| 42 | ||
| 43 | 1 | return locals; |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Check if `path` looks absolute. | |
| 48 | * | |
| 49 | * @param {String} path | |
| 50 | * @return {Boolean} | |
| 51 | * @api private | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | exports.isAbsolute = function(path){ |
| 55 | 0 | if ('/' == path[0]) return true; |
| 56 | 0 | if (':' == path[1] && '\\' == path[2]) return true; |
| 57 | 0 | if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path |
| 58 | }; | |
| 59 | ||
| 60 | /** | |
| 61 | * Flatten the given `arr`. | |
| 62 | * | |
| 63 | * @param {Array} arr | |
| 64 | * @return {Array} | |
| 65 | * @api private | |
| 66 | */ | |
| 67 | ||
| 68 | 1 | exports.flatten = function(arr, ret){ |
| 69 | 0 | var ret = ret || [] |
| 70 | , len = arr.length; | |
| 71 | 0 | for (var i = 0; i < len; ++i) { |
| 72 | 0 | if (Array.isArray(arr[i])) { |
| 73 | 0 | exports.flatten(arr[i], ret); |
| 74 | } else { | |
| 75 | 0 | ret.push(arr[i]); |
| 76 | } | |
| 77 | } | |
| 78 | 0 | return ret; |
| 79 | }; | |
| 80 | ||
| 81 | /** | |
| 82 | * Normalize the given `type`, for example "html" becomes "text/html". | |
| 83 | * | |
| 84 | * @param {String} type | |
| 85 | * @return {Object} | |
| 86 | * @api private | |
| 87 | */ | |
| 88 | ||
| 89 | 1 | exports.normalizeType = function(type){ |
| 90 | 0 | return ~type.indexOf('/') |
| 91 | ? acceptParams(type) | |
| 92 | : { value: mime.lookup(type), params: {} }; | |
| 93 | }; | |
| 94 | ||
| 95 | /** | |
| 96 | * Normalize `types`, for example "html" becomes "text/html". | |
| 97 | * | |
| 98 | * @param {Array} types | |
| 99 | * @return {Array} | |
| 100 | * @api private | |
| 101 | */ | |
| 102 | ||
| 103 | 1 | exports.normalizeTypes = function(types){ |
| 104 | 0 | var ret = []; |
| 105 | ||
| 106 | 0 | for (var i = 0; i < types.length; ++i) { |
| 107 | 0 | ret.push(exports.normalizeType(types[i])); |
| 108 | } | |
| 109 | ||
| 110 | 0 | return ret; |
| 111 | }; | |
| 112 | ||
| 113 | /** | |
| 114 | * Return the acceptable type in `types`, if any. | |
| 115 | * | |
| 116 | * @param {Array} types | |
| 117 | * @param {String} str | |
| 118 | * @return {String} | |
| 119 | * @api private | |
| 120 | */ | |
| 121 | ||
| 122 | 1 | exports.acceptsArray = function(types, str){ |
| 123 | // accept anything when Accept is not present | |
| 124 | 0 | if (!str) return types[0]; |
| 125 | ||
| 126 | // parse | |
| 127 | 0 | var accepted = exports.parseAccept(str) |
| 128 | , normalized = exports.normalizeTypes(types) | |
| 129 | , len = accepted.length; | |
| 130 | ||
| 131 | 0 | for (var i = 0; i < len; ++i) { |
| 132 | 0 | for (var j = 0, jlen = types.length; j < jlen; ++j) { |
| 133 | 0 | if (exports.accept(normalized[j], accepted[i])) { |
| 134 | 0 | return types[j]; |
| 135 | } | |
| 136 | } | |
| 137 | } | |
| 138 | }; | |
| 139 | ||
| 140 | /** | |
| 141 | * Check if `type(s)` are acceptable based on | |
| 142 | * the given `str`. | |
| 143 | * | |
| 144 | * @param {String|Array} type(s) | |
| 145 | * @param {String} str | |
| 146 | * @return {Boolean|String} | |
| 147 | * @api private | |
| 148 | */ | |
| 149 | ||
| 150 | 1 | exports.accepts = function(type, str){ |
| 151 | 0 | if ('string' == typeof type) type = type.split(/ *, */); |
| 152 | 0 | return exports.acceptsArray(type, str); |
| 153 | }; | |
| 154 | ||
| 155 | /** | |
| 156 | * Check if `type` array is acceptable for `other`. | |
| 157 | * | |
| 158 | * @param {Object} type | |
| 159 | * @param {Object} other | |
| 160 | * @return {Boolean} | |
| 161 | * @api private | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | exports.accept = function(type, other){ |
| 165 | 0 | var t = type.value.split('/'); |
| 166 | 0 | return (t[0] == other.type || '*' == other.type) |
| 167 | && (t[1] == other.subtype || '*' == other.subtype) | |
| 168 | && paramsEqual(type.params, other.params); | |
| 169 | }; | |
| 170 | ||
| 171 | /** | |
| 172 | * Check if accept params are equal. | |
| 173 | * | |
| 174 | * @param {Object} a | |
| 175 | * @param {Object} b | |
| 176 | * @return {Boolean} | |
| 177 | * @api private | |
| 178 | */ | |
| 179 | ||
| 180 | 1 | function paramsEqual(a, b){ |
| 181 | 0 | return !Object.keys(a).some(function(k) { |
| 182 | 0 | return a[k] != b[k]; |
| 183 | }); | |
| 184 | } | |
| 185 | ||
| 186 | /** | |
| 187 | * Parse accept `str`, returning | |
| 188 | * an array objects containing | |
| 189 | * `.type` and `.subtype` along | |
| 190 | * with the values provided by | |
| 191 | * `parseQuality()`. | |
| 192 | * | |
| 193 | * @param {Type} name | |
| 194 | * @return {Type} | |
| 195 | * @api private | |
| 196 | */ | |
| 197 | ||
| 198 | 1 | exports.parseAccept = function(str){ |
| 199 | 0 | return exports |
| 200 | .parseParams(str) | |
| 201 | .map(function(obj){ | |
| 202 | 0 | var parts = obj.value.split('/'); |
| 203 | 0 | obj.type = parts[0]; |
| 204 | 0 | obj.subtype = parts[1]; |
| 205 | 0 | return obj; |
| 206 | }); | |
| 207 | }; | |
| 208 | ||
| 209 | /** | |
| 210 | * Parse quality `str`, returning an | |
| 211 | * array of objects with `.value`, | |
| 212 | * `.quality` and optional `.params` | |
| 213 | * | |
| 214 | * @param {String} str | |
| 215 | * @return {Array} | |
| 216 | * @api private | |
| 217 | */ | |
| 218 | ||
| 219 | 1 | exports.parseParams = function(str){ |
| 220 | 0 | return str |
| 221 | .split(/ *, */) | |
| 222 | .map(acceptParams) | |
| 223 | .filter(function(obj){ | |
| 224 | 0 | return obj.quality; |
| 225 | }) | |
| 226 | .sort(function(a, b){ | |
| 227 | 0 | if (a.quality === b.quality) { |
| 228 | 0 | return a.originalIndex - b.originalIndex; |
| 229 | } else { | |
| 230 | 0 | return b.quality - a.quality; |
| 231 | } | |
| 232 | }); | |
| 233 | }; | |
| 234 | ||
| 235 | /** | |
| 236 | * Parse accept params `str` returning an | |
| 237 | * object with `.value`, `.quality` and `.params`. | |
| 238 | * also includes `.originalIndex` for stable sorting | |
| 239 | * | |
| 240 | * @param {String} str | |
| 241 | * @return {Object} | |
| 242 | * @api private | |
| 243 | */ | |
| 244 | ||
| 245 | 1 | function acceptParams(str, index) { |
| 246 | 0 | var parts = str.split(/ *; */); |
| 247 | 0 | var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index }; |
| 248 | ||
| 249 | 0 | for (var i = 1; i < parts.length; ++i) { |
| 250 | 0 | var pms = parts[i].split(/ *= */); |
| 251 | 0 | if ('q' == pms[0]) { |
| 252 | 0 | ret.quality = parseFloat(pms[1]); |
| 253 | } else { | |
| 254 | 0 | ret.params[pms[0]] = pms[1]; |
| 255 | } | |
| 256 | } | |
| 257 | ||
| 258 | 0 | return ret; |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Escape special characters in the given string of html. | |
| 263 | * | |
| 264 | * @param {String} html | |
| 265 | * @return {String} | |
| 266 | * @api private | |
| 267 | */ | |
| 268 | ||
| 269 | 1 | exports.escape = function(html) { |
| 270 | 0 | return String(html) |
| 271 | .replace(/&/g, '&') | |
| 272 | .replace(/"/g, '"') | |
| 273 | .replace(/</g, '<') | |
| 274 | .replace(/>/g, '>'); | |
| 275 | }; | |
| 276 | ||
| 277 | /** | |
| 278 | * Normalize the given path string, | |
| 279 | * returning a regular expression. | |
| 280 | * | |
| 281 | * An empty array should be passed, | |
| 282 | * which will contain the placeholder | |
| 283 | * key names. For example "/user/:id" will | |
| 284 | * then contain ["id"]. | |
| 285 | * | |
| 286 | * @param {String|RegExp|Array} path | |
| 287 | * @param {Array} keys | |
| 288 | * @param {Boolean} sensitive | |
| 289 | * @param {Boolean} strict | |
| 290 | * @return {RegExp} | |
| 291 | * @api private | |
| 292 | */ | |
| 293 | ||
| 294 | 1 | exports.pathRegexp = function(path, keys, sensitive, strict) { |
| 295 | 0 | if (toString.call(path) == '[object RegExp]') return path; |
| 296 | 0 | if (Array.isArray(path)) path = '(' + path.join('|') + ')'; |
| 297 | 0 | path = path |
| 298 | .concat(strict ? '' : '/?') | |
| 299 | .replace(/\/\(/g, '(?:/') | |
| 300 | .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){ | |
| 301 | 0 | keys.push({ name: key, optional: !! optional }); |
| 302 | 0 | slash = slash || ''; |
| 303 | 0 | return '' |
| 304 | + (optional ? '' : slash) | |
| 305 | + '(?:' | |
| 306 | + (optional ? slash : '') | |
| 307 | + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')' | |
| 308 | + (optional || '') | |
| 309 | + (star ? '(/*)?' : ''); | |
| 310 | }) | |
| 311 | .replace(/([\/.])/g, '\\$1') | |
| 312 | .replace(/\*/g, '(.*)'); | |
| 313 | 0 | return new RegExp('^' + path + '$', sensitive ? '' : 'i'); |
| 314 | } | |
| 315 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var path = require('path') |
| 6 | , fs = require('fs') | |
| 7 | , utils = require('./utils') | |
| 8 | , dirname = path.dirname | |
| 9 | , basename = path.basename | |
| 10 | , extname = path.extname | |
| 11 | , exists = fs.existsSync || path.existsSync | |
| 12 | , join = path.join; | |
| 13 | ||
| 14 | /** | |
| 15 | * Expose `View`. | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | module.exports = View; |
| 19 | ||
| 20 | /** | |
| 21 | * Initialize a new `View` with the given `name`. | |
| 22 | * | |
| 23 | * Options: | |
| 24 | * | |
| 25 | * - `defaultEngine` the default template engine name | |
| 26 | * - `engines` template engine require() cache | |
| 27 | * - `root` root path for view lookup | |
| 28 | * | |
| 29 | * @param {String} name | |
| 30 | * @param {Object} options | |
| 31 | * @api private | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | function View(name, options) { |
| 35 | 0 | options = options || {}; |
| 36 | 0 | this.name = name; |
| 37 | 0 | this.root = options.root; |
| 38 | 0 | var engines = options.engines; |
| 39 | 0 | this.defaultEngine = options.defaultEngine; |
| 40 | 0 | var ext = this.ext = extname(name); |
| 41 | 0 | if (!ext && !this.defaultEngine) throw new Error('No default engine was specified and no extension was provided.'); |
| 42 | 0 | if (!ext) name += (ext = this.ext = ('.' != this.defaultEngine[0] ? '.' : '') + this.defaultEngine); |
| 43 | 0 | this.engine = engines[ext] || (engines[ext] = require(ext.slice(1)).__express); |
| 44 | 0 | this.path = this.lookup(name); |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Lookup view by the given `path` | |
| 49 | * | |
| 50 | * @param {String} path | |
| 51 | * @return {String} | |
| 52 | * @api private | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | View.prototype.lookup = function(path){ |
| 56 | 0 | var ext = this.ext; |
| 57 | ||
| 58 | // <path>.<engine> | |
| 59 | 0 | if (!utils.isAbsolute(path)) path = join(this.root, path); |
| 60 | 0 | if (exists(path)) return path; |
| 61 | ||
| 62 | // <path>/index.<engine> | |
| 63 | 0 | path = join(dirname(path), basename(path, ext), 'index' + ext); |
| 64 | 0 | if (exists(path)) return path; |
| 65 | }; | |
| 66 | ||
| 67 | /** | |
| 68 | * Render with the given `options` and callback `fn(err, str)`. | |
| 69 | * | |
| 70 | * @param {Object} options | |
| 71 | * @param {Function} fn | |
| 72 | * @api private | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | View.prototype.render = function(options, fn){ |
| 76 | 0 | this.engine(this.path, options, fn); |
| 77 | }; | |
| 78 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Connect | |
| 3 | * Copyright(c) 2010 Sencha Inc. | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var EventEmitter = require('events').EventEmitter |
| 13 | , proto = require('./proto') | |
| 14 | , utils = require('./utils') | |
| 15 | , path = require('path') | |
| 16 | , basename = path.basename | |
| 17 | , fs = require('fs'); | |
| 18 | ||
| 19 | // node patches | |
| 20 | ||
| 21 | 1 | require('./patch'); |
| 22 | ||
| 23 | // expose createServer() as the module | |
| 24 | ||
| 25 | 1 | exports = module.exports = createServer; |
| 26 | ||
| 27 | /** | |
| 28 | * Framework version. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | exports.version = '2.7.11'; |
| 32 | ||
| 33 | /** | |
| 34 | * Expose mime module. | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | exports.mime = require('./middleware/static').mime; |
| 38 | ||
| 39 | /** | |
| 40 | * Expose the prototype. | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | exports.proto = proto; |
| 44 | ||
| 45 | /** | |
| 46 | * Auto-load middleware getters. | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | exports.middleware = {}; |
| 50 | ||
| 51 | /** | |
| 52 | * Expose utilities. | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | exports.utils = utils; |
| 56 | ||
| 57 | /** | |
| 58 | * Create a new connect server. | |
| 59 | * | |
| 60 | * @return {Function} | |
| 61 | * @api public | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | function createServer() { |
| 65 | 1 | function app(req, res, next){ app.handle(req, res, next); } |
| 66 | 1 | utils.merge(app, proto); |
| 67 | 1 | utils.merge(app, EventEmitter.prototype); |
| 68 | 1 | app.route = '/'; |
| 69 | 1 | app.stack = []; |
| 70 | 1 | for (var i = 0; i < arguments.length; ++i) { |
| 71 | 0 | app.use(arguments[i]); |
| 72 | } | |
| 73 | 1 | return app; |
| 74 | }; | |
| 75 | ||
| 76 | /** | |
| 77 | * Support old `.createServer()` method. | |
| 78 | */ | |
| 79 | ||
| 80 | 1 | createServer.createServer = createServer; |
| 81 | ||
| 82 | /** | |
| 83 | * Auto-load bundled middleware with getters. | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | fs.readdirSync(__dirname + '/middleware').forEach(function(filename){ |
| 87 | 24 | if (!/\.js$/.test(filename)) return; |
| 88 | 22 | var name = basename(filename, '.js'); |
| 89 | 24 | function load(){ return require('./middleware/' + name); } |
| 90 | 22 | exports.middleware.__defineGetter__(name, load); |
| 91 | 22 | exports.__defineGetter__(name, load); |
| 92 | }); | |
| 93 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Connect - errorHandler | |
| 3 | * Copyright(c) 2010 Sencha Inc. | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var utils = require('../utils') |
| 13 | , fs = require('fs'); | |
| 14 | ||
| 15 | // environment | |
| 16 | ||
| 17 | 1 | var env = process.env.NODE_ENV || 'development'; |
| 18 | ||
| 19 | /** | |
| 20 | * Error handler: | |
| 21 | * | |
| 22 | * Development error handler, providing stack traces | |
| 23 | * and error message responses for requests accepting text, html, | |
| 24 | * or json. | |
| 25 | * | |
| 26 | * Text: | |
| 27 | * | |
| 28 | * By default, and when _text/plain_ is accepted a simple stack trace | |
| 29 | * or error message will be returned. | |
| 30 | * | |
| 31 | * JSON: | |
| 32 | * | |
| 33 | * When _application/json_ is accepted, connect will respond with | |
| 34 | * an object in the form of `{ "error": error }`. | |
| 35 | * | |
| 36 | * HTML: | |
| 37 | * | |
| 38 | * When accepted connect will output a nice html stack trace. | |
| 39 | * | |
| 40 | * @return {Function} | |
| 41 | * @api public | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | exports = module.exports = function errorHandler(){ |
| 45 | 0 | return function errorHandler(err, req, res, next){ |
| 46 | 0 | if (err.status) res.statusCode = err.status; |
| 47 | 0 | if (res.statusCode < 400) res.statusCode = 500; |
| 48 | 0 | if ('test' != env) console.error(err.stack); |
| 49 | 0 | var accept = req.headers.accept || ''; |
| 50 | // html | |
| 51 | 0 | if (~accept.indexOf('html')) { |
| 52 | 0 | fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){ |
| 53 | 0 | fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){ |
| 54 | 0 | var stack = (err.stack || '') |
| 55 | .split('\n').slice(1) | |
| 56 | 0 | .map(function(v){ return '<li>' + v + '</li>'; }).join(''); |
| 57 | 0 | html = html |
| 58 | .replace('{style}', style) | |
| 59 | .replace('{stack}', stack) | |
| 60 | .replace('{title}', exports.title) | |
| 61 | .replace('{statusCode}', res.statusCode) | |
| 62 | .replace(/\{error\}/g, utils.escape(err.toString().replace(/\n/g, '<br/>'))); | |
| 63 | 0 | res.setHeader('Content-Type', 'text/html; charset=utf-8'); |
| 64 | 0 | res.end(html); |
| 65 | }); | |
| 66 | }); | |
| 67 | // json | |
| 68 | 0 | } else if (~accept.indexOf('json')) { |
| 69 | 0 | var error = { message: err.message, stack: err.stack }; |
| 70 | 0 | for (var prop in err) error[prop] = err[prop]; |
| 71 | 0 | var json = JSON.stringify({ error: error }); |
| 72 | 0 | res.setHeader('Content-Type', 'application/json'); |
| 73 | 0 | res.end(json); |
| 74 | // plain text | |
| 75 | } else { | |
| 76 | 0 | res.setHeader('Content-Type', 'text/plain'); |
| 77 | 0 | res.end(err.stack); |
| 78 | } | |
| 79 | }; | |
| 80 | }; | |
| 81 | ||
| 82 | /** | |
| 83 | * Template title, framework authors may override this value. | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | exports.title = 'Connect'; |
| 87 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Connect - query | |
| 3 | * Copyright(c) 2011 TJ Holowaychuk | |
| 4 | * Copyright(c) 2011 Sencha Inc. | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var qs = require('qs') |
| 13 | , parse = require('../utils').parseUrl; | |
| 14 | ||
| 15 | /** | |
| 16 | * Query: | |
| 17 | * | |
| 18 | * Automatically parse the query-string when available, | |
| 19 | * populating the `req.query` object using | |
| 20 | * [qs](https://github.com/visionmedia/node-querystring). | |
| 21 | * | |
| 22 | * Examples: | |
| 23 | * | |
| 24 | * connect() | |
| 25 | * .use(connect.query()) | |
| 26 | * .use(function(req, res){ | |
| 27 | * res.end(JSON.stringify(req.query)); | |
| 28 | * }); | |
| 29 | * | |
| 30 | * The `options` passed are provided to qs.parse function. | |
| 31 | * | |
| 32 | * @param {Object} options | |
| 33 | * @return {Function} | |
| 34 | * @api public | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | module.exports = function query(options){ |
| 38 | 1 | return function query(req, res, next){ |
| 39 | 0 | if (!req.query) { |
| 40 | 0 | req.query = ~req.url.indexOf('?') |
| 41 | ? qs.parse(parse(req).query, options) | |
| 42 | : {}; | |
| 43 | } | |
| 44 | ||
| 45 | 0 | next(); |
| 46 | }; | |
| 47 | }; | |
| 48 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Connect - static | |
| 3 | * Copyright(c) 2010 Sencha Inc. | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var send = require('send') |
| 13 | , utils = require('../utils') | |
| 14 | , parse = utils.parseUrl | |
| 15 | , url = require('url'); | |
| 16 | ||
| 17 | /** | |
| 18 | * Static: | |
| 19 | * | |
| 20 | * Static file server with the given `root` path. | |
| 21 | * | |
| 22 | * Examples: | |
| 23 | * | |
| 24 | * var oneDay = 86400000; | |
| 25 | * | |
| 26 | * connect() | |
| 27 | * .use(connect.static(__dirname + '/public')) | |
| 28 | * | |
| 29 | * connect() | |
| 30 | * .use(connect.static(__dirname + '/public', { maxAge: oneDay })) | |
| 31 | * | |
| 32 | * Options: | |
| 33 | * | |
| 34 | * - `maxAge` Browser cache maxAge in milliseconds. defaults to 0 | |
| 35 | * - `hidden` Allow transfer of hidden files. defaults to false | |
| 36 | * - `redirect` Redirect to trailing "/" when the pathname is a dir. defaults to true | |
| 37 | * - `index` Default file name, defaults to 'index.html' | |
| 38 | * | |
| 39 | * @param {String} root | |
| 40 | * @param {Object} options | |
| 41 | * @return {Function} | |
| 42 | * @api public | |
| 43 | */ | |
| 44 | ||
| 45 | 1 | exports = module.exports = function(root, options){ |
| 46 | 0 | options = options || {}; |
| 47 | ||
| 48 | // root required | |
| 49 | 0 | if (!root) throw new Error('static() root path required'); |
| 50 | ||
| 51 | // default redirect | |
| 52 | 0 | var redirect = false !== options.redirect; |
| 53 | ||
| 54 | 0 | return function staticMiddleware(req, res, next) { |
| 55 | 0 | if ('GET' != req.method && 'HEAD' != req.method) return next(); |
| 56 | 0 | var originalUrl = url.parse(req.originalUrl); |
| 57 | 0 | var path = parse(req).pathname; |
| 58 | 0 | var pause = utils.pause(req); |
| 59 | ||
| 60 | 0 | if (path == '/' && originalUrl.pathname[originalUrl.pathname.length - 1] != '/') { |
| 61 | 0 | return directory(); |
| 62 | } | |
| 63 | ||
| 64 | 0 | function resume() { |
| 65 | 0 | next(); |
| 66 | 0 | pause.resume(); |
| 67 | } | |
| 68 | ||
| 69 | 0 | function directory() { |
| 70 | 0 | if (!redirect) return resume(); |
| 71 | 0 | var target; |
| 72 | 0 | originalUrl.pathname += '/'; |
| 73 | 0 | target = url.format(originalUrl); |
| 74 | 0 | res.statusCode = 303; |
| 75 | 0 | res.setHeader('Location', target); |
| 76 | 0 | res.end('Redirecting to ' + utils.escape(target)); |
| 77 | } | |
| 78 | ||
| 79 | 0 | function error(err) { |
| 80 | 0 | if (404 == err.status) return resume(); |
| 81 | 0 | next(err); |
| 82 | } | |
| 83 | ||
| 84 | 0 | send(req, path) |
| 85 | .maxage(options.maxAge || 0) | |
| 86 | .root(root) | |
| 87 | .index(options.index || 'index.html') | |
| 88 | .hidden(options.hidden) | |
| 89 | .on('error', error) | |
| 90 | .on('directory', directory) | |
| 91 | .pipe(res); | |
| 92 | }; | |
| 93 | }; | |
| 94 | ||
| 95 | /** | |
| 96 | * Expose mime module. | |
| 97 | * | |
| 98 | * If you wish to extend the mime table use this | |
| 99 | * reference to the "mime" module in the npm registry. | |
| 100 | */ | |
| 101 | ||
| 102 | 1 | exports.mime = send.mime; |
| 103 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var http = require('http') |
| 13 | , res = http.ServerResponse.prototype | |
| 14 | , setHeader = res.setHeader | |
| 15 | , _renderHeaders = res._renderHeaders | |
| 16 | , writeHead = res.writeHead; | |
| 17 | ||
| 18 | // apply only once | |
| 19 | ||
| 20 | 1 | if (!res._hasConnectPatch) { |
| 21 | ||
| 22 | /** | |
| 23 | * Provide a public "header sent" flag | |
| 24 | * until node does. | |
| 25 | * | |
| 26 | * @return {Boolean} | |
| 27 | * @api public | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | res.__defineGetter__('headerSent', function(){ |
| 31 | 0 | return this._header; |
| 32 | }); | |
| 33 | ||
| 34 | /** | |
| 35 | * Set header `field` to `val`, special-casing | |
| 36 | * the `Set-Cookie` field for multiple support. | |
| 37 | * | |
| 38 | * @param {String} field | |
| 39 | * @param {String} val | |
| 40 | * @api public | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | res.setHeader = function(field, val){ |
| 44 | 0 | var key = field.toLowerCase() |
| 45 | , prev; | |
| 46 | ||
| 47 | // special-case Set-Cookie | |
| 48 | 0 | if (this._headers && 'set-cookie' == key) { |
| 49 | 0 | if (prev = this.getHeader(field)) { |
| 50 | 0 | if (Array.isArray(prev)) { |
| 51 | 0 | val = prev.concat(val); |
| 52 | 0 | } else if (Array.isArray(val)) { |
| 53 | 0 | val = val.concat(prev); |
| 54 | } else { | |
| 55 | 0 | val = [prev, val]; |
| 56 | } | |
| 57 | } | |
| 58 | // charset | |
| 59 | 0 | } else if ('content-type' == key && this.charset) { |
| 60 | 0 | val += '; charset=' + this.charset; |
| 61 | } | |
| 62 | ||
| 63 | 0 | return setHeader.call(this, field, val); |
| 64 | }; | |
| 65 | ||
| 66 | /** | |
| 67 | * Proxy to emit "header" event. | |
| 68 | */ | |
| 69 | ||
| 70 | 1 | res._renderHeaders = function(){ |
| 71 | 0 | if (!this._emittedHeader) this.emit('header'); |
| 72 | 0 | this._emittedHeader = true; |
| 73 | 0 | return _renderHeaders.call(this); |
| 74 | }; | |
| 75 | ||
| 76 | 1 | res.writeHead = function(statusCode, reasonPhrase, headers){ |
| 77 | 0 | if (typeof reasonPhrase === 'object') headers = reasonPhrase; |
| 78 | 0 | if (typeof headers === 'object') { |
| 79 | 0 | Object.keys(headers).forEach(function(key){ |
| 80 | 0 | this.setHeader(key, headers[key]); |
| 81 | }, this); | |
| 82 | } | |
| 83 | 0 | if (!this._emittedHeader) this.emit('header'); |
| 84 | 0 | this._emittedHeader = true; |
| 85 | 0 | return writeHead.call(this, statusCode, reasonPhrase); |
| 86 | }; | |
| 87 | ||
| 88 | 1 | res._hasConnectPatch = true; |
| 89 | } | |
| 90 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Connect - HTTPServer | |
| 3 | * Copyright(c) 2010 Sencha Inc. | |
| 4 | * Copyright(c) 2011 TJ Holowaychuk | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var http = require('http') |
| 13 | , utils = require('./utils') | |
| 14 | , debug = require('debug')('connect:dispatcher'); | |
| 15 | ||
| 16 | // prototype | |
| 17 | ||
| 18 | 1 | var app = module.exports = {}; |
| 19 | ||
| 20 | // environment | |
| 21 | ||
| 22 | 1 | var env = process.env.NODE_ENV || 'development'; |
| 23 | ||
| 24 | /** | |
| 25 | * Utilize the given middleware `handle` to the given `route`, | |
| 26 | * defaulting to _/_. This "route" is the mount-point for the | |
| 27 | * middleware, when given a value other than _/_ the middleware | |
| 28 | * is only effective when that segment is present in the request's | |
| 29 | * pathname. | |
| 30 | * | |
| 31 | * For example if we were to mount a function at _/admin_, it would | |
| 32 | * be invoked on _/admin_, and _/admin/settings_, however it would | |
| 33 | * not be invoked for _/_, or _/posts_. | |
| 34 | * | |
| 35 | * Examples: | |
| 36 | * | |
| 37 | * var app = connect(); | |
| 38 | * app.use(connect.favicon()); | |
| 39 | * app.use(connect.logger()); | |
| 40 | * app.use(connect.static(__dirname + '/public')); | |
| 41 | * | |
| 42 | * If we wanted to prefix static files with _/public_, we could | |
| 43 | * "mount" the `static()` middleware: | |
| 44 | * | |
| 45 | * app.use('/public', connect.static(__dirname + '/public')); | |
| 46 | * | |
| 47 | * This api is chainable, so the following is valid: | |
| 48 | * | |
| 49 | * connect() | |
| 50 | * .use(connect.favicon()) | |
| 51 | * .use(connect.logger()) | |
| 52 | * .use(connect.static(__dirname + '/public')) | |
| 53 | * .listen(3000); | |
| 54 | * | |
| 55 | * @param {String|Function|Server} route, callback or server | |
| 56 | * @param {Function|Server} callback or server | |
| 57 | * @return {Server} for chaining | |
| 58 | * @api public | |
| 59 | */ | |
| 60 | ||
| 61 | 1 | app.use = function(route, fn){ |
| 62 | // default route to '/' | |
| 63 | 2 | if ('string' != typeof route) { |
| 64 | 0 | fn = route; |
| 65 | 0 | route = '/'; |
| 66 | } | |
| 67 | ||
| 68 | // wrap sub-apps | |
| 69 | 2 | if ('function' == typeof fn.handle) { |
| 70 | 0 | var server = fn; |
| 71 | 0 | fn.route = route; |
| 72 | 0 | fn = function(req, res, next){ |
| 73 | 0 | server.handle(req, res, next); |
| 74 | }; | |
| 75 | } | |
| 76 | ||
| 77 | // wrap vanilla http.Servers | |
| 78 | 2 | if (fn instanceof http.Server) { |
| 79 | 0 | fn = fn.listeners('request')[0]; |
| 80 | } | |
| 81 | ||
| 82 | // strip trailing slash | |
| 83 | 2 | if ('/' == route[route.length - 1]) { |
| 84 | 2 | route = route.slice(0, -1); |
| 85 | } | |
| 86 | ||
| 87 | // add the middleware | |
| 88 | 2 | debug('use %s %s', route || '/', fn.name || 'anonymous'); |
| 89 | 2 | this.stack.push({ route: route, handle: fn }); |
| 90 | ||
| 91 | 2 | return this; |
| 92 | }; | |
| 93 | ||
| 94 | /** | |
| 95 | * Handle server requests, punting them down | |
| 96 | * the middleware stack. | |
| 97 | * | |
| 98 | * @api private | |
| 99 | */ | |
| 100 | ||
| 101 | 1 | app.handle = function(req, res, out) { |
| 102 | 0 | var stack = this.stack |
| 103 | , search = 1 + req.url.indexOf('?') | |
| 104 | , pathlength = search ? search - 1 : req.url.length | |
| 105 | , fqdn = 1 + req.url.substr(0, pathlength).indexOf('://') | |
| 106 | , protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '' | |
| 107 | , removed = '' | |
| 108 | , slashAdded = false | |
| 109 | , index = 0; | |
| 110 | ||
| 111 | 0 | function next(err) { |
| 112 | 0 | var layer, path, c; |
| 113 | ||
| 114 | 0 | if (slashAdded) { |
| 115 | 0 | req.url = req.url.substr(1); |
| 116 | 0 | slashAdded = false; |
| 117 | } | |
| 118 | ||
| 119 | 0 | req.url = protohost + removed + req.url.substr(protohost.length); |
| 120 | 0 | req.originalUrl = req.originalUrl || req.url; |
| 121 | 0 | removed = ''; |
| 122 | ||
| 123 | // next callback | |
| 124 | 0 | layer = stack[index++]; |
| 125 | ||
| 126 | // all done | |
| 127 | 0 | if (!layer || res.headerSent) { |
| 128 | // delegate to parent | |
| 129 | 0 | if (out) return out(err); |
| 130 | ||
| 131 | // unhandled error | |
| 132 | 0 | if (err) { |
| 133 | // default to 500 | |
| 134 | 0 | if (res.statusCode < 400) res.statusCode = 500; |
| 135 | 0 | debug('default %s', res.statusCode); |
| 136 | ||
| 137 | // respect err.status | |
| 138 | 0 | if (err.status) res.statusCode = err.status; |
| 139 | ||
| 140 | // production gets a basic error message | |
| 141 | 0 | var msg = 'production' == env |
| 142 | ? http.STATUS_CODES[res.statusCode] | |
| 143 | : err.stack || err.toString(); | |
| 144 | 0 | msg = utils.escape(msg); |
| 145 | ||
| 146 | // log to stderr in a non-test env | |
| 147 | 0 | if ('test' != env) console.error(err.stack || err.toString()); |
| 148 | 0 | if (res.headerSent) return req.socket.destroy(); |
| 149 | 0 | res.setHeader('Content-Type', 'text/html'); |
| 150 | 0 | res.setHeader('Content-Length', Buffer.byteLength(msg)); |
| 151 | 0 | if ('HEAD' == req.method) return res.end(); |
| 152 | 0 | res.end(msg); |
| 153 | } else { | |
| 154 | 0 | debug('default 404'); |
| 155 | 0 | res.statusCode = 404; |
| 156 | 0 | res.setHeader('Content-Type', 'text/html'); |
| 157 | 0 | if ('HEAD' == req.method) return res.end(); |
| 158 | 0 | res.end('Cannot ' + utils.escape(req.method) + ' ' + utils.escape(req.originalUrl) + '\n'); |
| 159 | } | |
| 160 | 0 | return; |
| 161 | } | |
| 162 | ||
| 163 | 0 | try { |
| 164 | 0 | path = utils.parseUrl(req).pathname; |
| 165 | 0 | if (undefined == path) path = '/'; |
| 166 | ||
| 167 | // skip this layer if the route doesn't match. | |
| 168 | 0 | if (0 != path.toLowerCase().indexOf(layer.route.toLowerCase())) return next(err); |
| 169 | ||
| 170 | 0 | c = path[layer.route.length]; |
| 171 | 0 | if (c && '/' != c && '.' != c) return next(err); |
| 172 | ||
| 173 | // Call the layer handler | |
| 174 | // Trim off the part of the url that matches the route | |
| 175 | 0 | removed = layer.route; |
| 176 | 0 | req.url = protohost + req.url.substr(protohost.length + removed.length); |
| 177 | ||
| 178 | // Ensure leading slash | |
| 179 | 0 | if (!fqdn && '/' != req.url[0]) { |
| 180 | 0 | req.url = '/' + req.url; |
| 181 | 0 | slashAdded = true; |
| 182 | } | |
| 183 | ||
| 184 | 0 | debug('%s %s : %s', layer.handle.name || 'anonymous', layer.route, req.originalUrl); |
| 185 | 0 | var arity = layer.handle.length; |
| 186 | 0 | if (err) { |
| 187 | 0 | if (arity === 4) { |
| 188 | 0 | layer.handle(err, req, res, next); |
| 189 | } else { | |
| 190 | 0 | next(err); |
| 191 | } | |
| 192 | 0 | } else if (arity < 4) { |
| 193 | 0 | layer.handle(req, res, next); |
| 194 | } else { | |
| 195 | 0 | next(); |
| 196 | } | |
| 197 | } catch (e) { | |
| 198 | 0 | next(e); |
| 199 | } | |
| 200 | } | |
| 201 | 0 | next(); |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * Listen for connections. | |
| 206 | * | |
| 207 | * This method takes the same arguments | |
| 208 | * as node's `http.Server#listen()`. | |
| 209 | * | |
| 210 | * HTTP and HTTPS: | |
| 211 | * | |
| 212 | * If you run your application both as HTTP | |
| 213 | * and HTTPS you may wrap them individually, | |
| 214 | * since your Connect "server" is really just | |
| 215 | * a JavaScript `Function`. | |
| 216 | * | |
| 217 | * var connect = require('connect') | |
| 218 | * , http = require('http') | |
| 219 | * , https = require('https'); | |
| 220 | * | |
| 221 | * var app = connect(); | |
| 222 | * | |
| 223 | * http.createServer(app).listen(80); | |
| 224 | * https.createServer(options, app).listen(443); | |
| 225 | * | |
| 226 | * @return {http.Server} | |
| 227 | * @api public | |
| 228 | */ | |
| 229 | ||
| 230 | 1 | app.listen = function(){ |
| 231 | 0 | var server = http.createServer(this); |
| 232 | 0 | return server.listen.apply(server, arguments); |
| 233 | }; | |
| 234 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connect - utils | |
| 4 | * Copyright(c) 2010 Sencha Inc. | |
| 5 | * Copyright(c) 2011 TJ Holowaychuk | |
| 6 | * MIT Licensed | |
| 7 | */ | |
| 8 | ||
| 9 | /** | |
| 10 | * Module dependencies. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | var http = require('http') |
| 14 | , crypto = require('crypto') | |
| 15 | , parse = require('url').parse | |
| 16 | , sep = require('path').sep | |
| 17 | , signature = require('cookie-signature') | |
| 18 | , nodeVersion = process.versions.node.split('.'); | |
| 19 | ||
| 20 | // pause is broken in node < 0.10 | |
| 21 | 1 | exports.brokenPause = parseInt(nodeVersion[0], 10) === 0 |
| 22 | && parseInt(nodeVersion[1], 10) < 10; | |
| 23 | ||
| 24 | /** | |
| 25 | * Return `true` if the request has a body, otherwise return `false`. | |
| 26 | * | |
| 27 | * @param {IncomingMessage} req | |
| 28 | * @return {Boolean} | |
| 29 | * @api private | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | exports.hasBody = function(req) { |
| 33 | 0 | var encoding = 'transfer-encoding' in req.headers; |
| 34 | 0 | var length = 'content-length' in req.headers && req.headers['content-length'] !== '0'; |
| 35 | 0 | return encoding || length; |
| 36 | }; | |
| 37 | ||
| 38 | /** | |
| 39 | * Extract the mime type from the given request's | |
| 40 | * _Content-Type_ header. | |
| 41 | * | |
| 42 | * @param {IncomingMessage} req | |
| 43 | * @return {String} | |
| 44 | * @api private | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | exports.mime = function(req) { |
| 48 | 0 | var str = req.headers['content-type'] || ''; |
| 49 | 0 | return str.split(';')[0]; |
| 50 | }; | |
| 51 | ||
| 52 | /** | |
| 53 | * Generate an `Error` from the given status `code` | |
| 54 | * and optional `msg`. | |
| 55 | * | |
| 56 | * @param {Number} code | |
| 57 | * @param {String} msg | |
| 58 | * @return {Error} | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | exports.error = function(code, msg){ |
| 63 | 0 | var err = new Error(msg || http.STATUS_CODES[code]); |
| 64 | 0 | err.status = code; |
| 65 | 0 | return err; |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Return md5 hash of the given string and optional encoding, | |
| 70 | * defaulting to hex. | |
| 71 | * | |
| 72 | * utils.md5('wahoo'); | |
| 73 | * // => "e493298061761236c96b02ea6aa8a2ad" | |
| 74 | * | |
| 75 | * @param {String} str | |
| 76 | * @param {String} encoding | |
| 77 | * @return {String} | |
| 78 | * @api private | |
| 79 | */ | |
| 80 | ||
| 81 | 1 | exports.md5 = function(str, encoding){ |
| 82 | 0 | return crypto |
| 83 | .createHash('md5') | |
| 84 | .update(str, 'utf8') | |
| 85 | .digest(encoding || 'hex'); | |
| 86 | }; | |
| 87 | ||
| 88 | /** | |
| 89 | * Merge object b with object a. | |
| 90 | * | |
| 91 | * var a = { foo: 'bar' } | |
| 92 | * , b = { bar: 'baz' }; | |
| 93 | * | |
| 94 | * utils.merge(a, b); | |
| 95 | * // => { foo: 'bar', bar: 'baz' } | |
| 96 | * | |
| 97 | * @param {Object} a | |
| 98 | * @param {Object} b | |
| 99 | * @return {Object} | |
| 100 | * @api private | |
| 101 | */ | |
| 102 | ||
| 103 | 1 | exports.merge = function(a, b){ |
| 104 | 3 | if (a && b) { |
| 105 | 3 | for (var key in b) { |
| 106 | 51 | a[key] = b[key]; |
| 107 | } | |
| 108 | } | |
| 109 | 3 | return a; |
| 110 | }; | |
| 111 | ||
| 112 | /** | |
| 113 | * Escape the given string of `html`. | |
| 114 | * | |
| 115 | * @param {String} html | |
| 116 | * @return {String} | |
| 117 | * @api private | |
| 118 | */ | |
| 119 | ||
| 120 | 1 | exports.escape = function(html){ |
| 121 | 0 | return String(html) |
| 122 | .replace(/&(?!\w+;)/g, '&') | |
| 123 | .replace(/</g, '<') | |
| 124 | .replace(/>/g, '>') | |
| 125 | .replace(/"/g, '"'); | |
| 126 | }; | |
| 127 | ||
| 128 | /** | |
| 129 | * Sign the given `val` with `secret`. | |
| 130 | * | |
| 131 | * @param {String} val | |
| 132 | * @param {String} secret | |
| 133 | * @return {String} | |
| 134 | * @api private | |
| 135 | */ | |
| 136 | ||
| 137 | 1 | exports.sign = function(val, secret){ |
| 138 | 0 | console.warn('do not use utils.sign(), use https://github.com/visionmedia/node-cookie-signature') |
| 139 | 0 | return val + '.' + crypto |
| 140 | .createHmac('sha256', secret) | |
| 141 | .update(val) | |
| 142 | .digest('base64') | |
| 143 | .replace(/=+$/, ''); | |
| 144 | }; | |
| 145 | ||
| 146 | /** | |
| 147 | * Unsign and decode the given `val` with `secret`, | |
| 148 | * returning `false` if the signature is invalid. | |
| 149 | * | |
| 150 | * @param {String} val | |
| 151 | * @param {String} secret | |
| 152 | * @return {String|Boolean} | |
| 153 | * @api private | |
| 154 | */ | |
| 155 | ||
| 156 | 1 | exports.unsign = function(val, secret){ |
| 157 | 0 | console.warn('do not use utils.unsign(), use https://github.com/visionmedia/node-cookie-signature') |
| 158 | 0 | var str = val.slice(0, val.lastIndexOf('.')); |
| 159 | 0 | return exports.sign(str, secret) == val |
| 160 | ? str | |
| 161 | : false; | |
| 162 | }; | |
| 163 | ||
| 164 | /** | |
| 165 | * Parse signed cookies, returning an object | |
| 166 | * containing the decoded key/value pairs, | |
| 167 | * while removing the signed key from `obj`. | |
| 168 | * | |
| 169 | * @param {Object} obj | |
| 170 | * @return {Object} | |
| 171 | * @api private | |
| 172 | */ | |
| 173 | ||
| 174 | 1 | exports.parseSignedCookies = function(obj, secret){ |
| 175 | 0 | var ret = {}; |
| 176 | 0 | Object.keys(obj).forEach(function(key){ |
| 177 | 0 | var val = obj[key]; |
| 178 | 0 | if (0 == val.indexOf('s:')) { |
| 179 | 0 | val = signature.unsign(val.slice(2), secret); |
| 180 | 0 | if (val) { |
| 181 | 0 | ret[key] = val; |
| 182 | 0 | delete obj[key]; |
| 183 | } | |
| 184 | } | |
| 185 | }); | |
| 186 | 0 | return ret; |
| 187 | }; | |
| 188 | ||
| 189 | /** | |
| 190 | * Parse a signed cookie string, return the decoded value | |
| 191 | * | |
| 192 | * @param {String} str signed cookie string | |
| 193 | * @param {String} secret | |
| 194 | * @return {String} decoded value | |
| 195 | * @api private | |
| 196 | */ | |
| 197 | ||
| 198 | 1 | exports.parseSignedCookie = function(str, secret){ |
| 199 | 0 | return 0 == str.indexOf('s:') |
| 200 | ? signature.unsign(str.slice(2), secret) | |
| 201 | : str; | |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * Parse JSON cookies. | |
| 206 | * | |
| 207 | * @param {Object} obj | |
| 208 | * @return {Object} | |
| 209 | * @api private | |
| 210 | */ | |
| 211 | ||
| 212 | 1 | exports.parseJSONCookies = function(obj){ |
| 213 | 0 | Object.keys(obj).forEach(function(key){ |
| 214 | 0 | var val = obj[key]; |
| 215 | 0 | var res = exports.parseJSONCookie(val); |
| 216 | 0 | if (res) obj[key] = res; |
| 217 | }); | |
| 218 | 0 | return obj; |
| 219 | }; | |
| 220 | ||
| 221 | /** | |
| 222 | * Parse JSON cookie string | |
| 223 | * | |
| 224 | * @param {String} str | |
| 225 | * @return {Object} Parsed object or null if not json cookie | |
| 226 | * @api private | |
| 227 | */ | |
| 228 | ||
| 229 | 1 | exports.parseJSONCookie = function(str) { |
| 230 | 0 | if (0 == str.indexOf('j:')) { |
| 231 | 0 | try { |
| 232 | 0 | return JSON.parse(str.slice(2)); |
| 233 | } catch (err) { | |
| 234 | // no op | |
| 235 | } | |
| 236 | } | |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Pause `data` and `end` events on the given `obj`. | |
| 241 | * Middleware performing async tasks _should_ utilize | |
| 242 | * this utility (or similar), to re-emit data once | |
| 243 | * the async operation has completed, otherwise these | |
| 244 | * events may be lost. Pause is only required for | |
| 245 | * node versions less than 10, and is replaced with | |
| 246 | * noop's otherwise. | |
| 247 | * | |
| 248 | * var pause = utils.pause(req); | |
| 249 | * fs.readFile(path, function(){ | |
| 250 | * next(); | |
| 251 | * pause.resume(); | |
| 252 | * }); | |
| 253 | * | |
| 254 | * @param {Object} obj | |
| 255 | * @return {Object} | |
| 256 | * @api private | |
| 257 | */ | |
| 258 | ||
| 259 | 1 | exports.pause = exports.brokenPause |
| 260 | ? require('pause') | |
| 261 | : function () { | |
| 262 | 0 | return { |
| 263 | end: noop, | |
| 264 | resume: noop | |
| 265 | } | |
| 266 | } | |
| 267 | ||
| 268 | /** | |
| 269 | * Strip `Content-*` headers from `res`. | |
| 270 | * | |
| 271 | * @param {ServerResponse} res | |
| 272 | * @api private | |
| 273 | */ | |
| 274 | ||
| 275 | 1 | exports.removeContentHeaders = function(res){ |
| 276 | 0 | if (!res._headers) return; |
| 277 | 0 | Object.keys(res._headers).forEach(function(field){ |
| 278 | 0 | if (0 == field.indexOf('content')) { |
| 279 | 0 | res.removeHeader(field); |
| 280 | } | |
| 281 | }); | |
| 282 | }; | |
| 283 | ||
| 284 | /** | |
| 285 | * Check if `req` is a conditional GET request. | |
| 286 | * | |
| 287 | * @param {IncomingMessage} req | |
| 288 | * @return {Boolean} | |
| 289 | * @api private | |
| 290 | */ | |
| 291 | ||
| 292 | 1 | exports.conditionalGET = function(req) { |
| 293 | 0 | return req.headers['if-modified-since'] |
| 294 | || req.headers['if-none-match']; | |
| 295 | }; | |
| 296 | ||
| 297 | /** | |
| 298 | * Respond with 401 "Unauthorized". | |
| 299 | * | |
| 300 | * @param {ServerResponse} res | |
| 301 | * @param {String} realm | |
| 302 | * @api private | |
| 303 | */ | |
| 304 | ||
| 305 | 1 | exports.unauthorized = function(res, realm) { |
| 306 | 0 | res.statusCode = 401; |
| 307 | 0 | res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); |
| 308 | 0 | res.end('Unauthorized'); |
| 309 | }; | |
| 310 | ||
| 311 | /** | |
| 312 | * Respond with 304 "Not Modified". | |
| 313 | * | |
| 314 | * @param {ServerResponse} res | |
| 315 | * @param {Object} headers | |
| 316 | * @api private | |
| 317 | */ | |
| 318 | ||
| 319 | 1 | exports.notModified = function(res) { |
| 320 | 0 | exports.removeContentHeaders(res); |
| 321 | 0 | res.statusCode = 304; |
| 322 | 0 | res.end(); |
| 323 | }; | |
| 324 | ||
| 325 | /** | |
| 326 | * Return an ETag in the form of `"<size>-<mtime>"` | |
| 327 | * from the given `stat`. | |
| 328 | * | |
| 329 | * @param {Object} stat | |
| 330 | * @return {String} | |
| 331 | * @api private | |
| 332 | */ | |
| 333 | ||
| 334 | 1 | exports.etag = function(stat) { |
| 335 | 0 | return '"' + stat.size + '-' + Number(stat.mtime) + '"'; |
| 336 | }; | |
| 337 | ||
| 338 | /** | |
| 339 | * Parse the given Cache-Control `str`. | |
| 340 | * | |
| 341 | * @param {String} str | |
| 342 | * @return {Object} | |
| 343 | * @api private | |
| 344 | */ | |
| 345 | ||
| 346 | 1 | exports.parseCacheControl = function(str){ |
| 347 | 0 | var directives = str.split(',') |
| 348 | , obj = {}; | |
| 349 | ||
| 350 | 0 | for(var i = 0, len = directives.length; i < len; i++) { |
| 351 | 0 | var parts = directives[i].split('=') |
| 352 | , key = parts.shift().trim() | |
| 353 | , val = parseInt(parts.shift(), 10); | |
| 354 | ||
| 355 | 0 | obj[key] = isNaN(val) ? true : val; |
| 356 | } | |
| 357 | ||
| 358 | 0 | return obj; |
| 359 | }; | |
| 360 | ||
| 361 | /** | |
| 362 | * Parse the `req` url with memoization. | |
| 363 | * | |
| 364 | * @param {ServerRequest} req | |
| 365 | * @return {Object} | |
| 366 | * @api private | |
| 367 | */ | |
| 368 | ||
| 369 | 1 | exports.parseUrl = function(req){ |
| 370 | 0 | var parsed = req._parsedUrl; |
| 371 | 0 | if (parsed && parsed.href == req.url) { |
| 372 | 0 | return parsed; |
| 373 | } else { | |
| 374 | 0 | parsed = parse(req.url); |
| 375 | ||
| 376 | 0 | if (parsed.auth && !parsed.protocol && ~parsed.href.indexOf('//')) { |
| 377 | // This parses pathnames, and a strange pathname like //r@e should work | |
| 378 | 0 | parsed = parse(req.url.replace(/@/g, '%40')); |
| 379 | } | |
| 380 | ||
| 381 | 0 | return req._parsedUrl = parsed; |
| 382 | } | |
| 383 | }; | |
| 384 | ||
| 385 | /** | |
| 386 | * Parse byte `size` string. | |
| 387 | * | |
| 388 | * @param {String} size | |
| 389 | * @return {Number} | |
| 390 | * @api private | |
| 391 | */ | |
| 392 | ||
| 393 | 1 | exports.parseBytes = require('bytes'); |
| 394 | ||
| 395 | /** | |
| 396 | * Normalizes the path separator from system separator | |
| 397 | * to URL separator, aka `/`. | |
| 398 | * | |
| 399 | * @param {String} path | |
| 400 | * @return {String} | |
| 401 | * @api private | |
| 402 | */ | |
| 403 | ||
| 404 | 1 | exports.normalizeSlashes = function normalizeSlashes(path) { |
| 405 | 0 | return path.split(sep).join('/'); |
| 406 | }; | |
| 407 | ||
| 408 | 1 | function noop() {} |
| 409 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var tty = require('tty'); |
| 6 | ||
| 7 | /** | |
| 8 | * Expose `debug()` as the module. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = debug; |
| 12 | ||
| 13 | /** | |
| 14 | * Enabled debuggers. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | var names = [] |
| 18 | , skips = []; | |
| 19 | ||
| 20 | 1 | (process.env.DEBUG || '') |
| 21 | .split(/[\s,]+/) | |
| 22 | .forEach(function(name){ | |
| 23 | 1 | name = name.replace('*', '.*?'); |
| 24 | 1 | if (name[0] === '-') { |
| 25 | 0 | skips.push(new RegExp('^' + name.substr(1) + '$')); |
| 26 | } else { | |
| 27 | 1 | names.push(new RegExp('^' + name + '$')); |
| 28 | } | |
| 29 | }); | |
| 30 | ||
| 31 | /** | |
| 32 | * Colors. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | var colors = [6, 2, 3, 4, 5, 1]; |
| 36 | ||
| 37 | /** | |
| 38 | * Previous debug() call. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | var prev = {}; |
| 42 | ||
| 43 | /** | |
| 44 | * Previously assigned color. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | var prevColor = 0; |
| 48 | ||
| 49 | /** | |
| 50 | * Is stdout a TTY? Colored output is disabled when `true`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | var isatty = tty.isatty(2); |
| 54 | ||
| 55 | /** | |
| 56 | * Select a color. | |
| 57 | * | |
| 58 | * @return {Number} | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | function color() { |
| 63 | 0 | return colors[prevColor++ % colors.length]; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Humanize the given `ms`. | |
| 68 | * | |
| 69 | * @param {Number} m | |
| 70 | * @return {String} | |
| 71 | * @api private | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | function humanize(ms) { |
| 75 | 0 | var sec = 1000 |
| 76 | , min = 60 * 1000 | |
| 77 | , hour = 60 * min; | |
| 78 | ||
| 79 | 0 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; |
| 80 | 0 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; |
| 81 | 0 | if (ms >= sec) return (ms / sec | 0) + 's'; |
| 82 | 0 | return ms + 'ms'; |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Create a debugger with the given `name`. | |
| 87 | * | |
| 88 | * @param {String} name | |
| 89 | * @return {Type} | |
| 90 | * @api public | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function debug(name) { |
| 94 | 4 | function disabled(){} |
| 95 | 4 | disabled.enabled = false; |
| 96 | ||
| 97 | 4 | var match = skips.some(function(re){ |
| 98 | 0 | return re.test(name); |
| 99 | }); | |
| 100 | ||
| 101 | 4 | if (match) return disabled; |
| 102 | ||
| 103 | 4 | match = names.some(function(re){ |
| 104 | 4 | return re.test(name); |
| 105 | }); | |
| 106 | ||
| 107 | 8 | if (!match) return disabled; |
| 108 | 0 | var c = color(); |
| 109 | ||
| 110 | 0 | function colored(fmt) { |
| 111 | 0 | fmt = coerce(fmt); |
| 112 | ||
| 113 | 0 | var curr = new Date; |
| 114 | 0 | var ms = curr - (prev[name] || curr); |
| 115 | 0 | prev[name] = curr; |
| 116 | ||
| 117 | 0 | fmt = ' \u001b[9' + c + 'm' + name + ' ' |
| 118 | + '\u001b[3' + c + 'm\u001b[90m' | |
| 119 | + fmt + '\u001b[3' + c + 'm' | |
| 120 | + ' +' + humanize(ms) + '\u001b[0m'; | |
| 121 | ||
| 122 | 0 | console.error.apply(this, arguments); |
| 123 | } | |
| 124 | ||
| 125 | 0 | function plain(fmt) { |
| 126 | 0 | fmt = coerce(fmt); |
| 127 | ||
| 128 | 0 | fmt = new Date().toUTCString() |
| 129 | + ' ' + name + ' ' + fmt; | |
| 130 | 0 | console.error.apply(this, arguments); |
| 131 | } | |
| 132 | ||
| 133 | 0 | colored.enabled = plain.enabled = true; |
| 134 | ||
| 135 | 0 | return isatty || process.env.DEBUG_COLORS |
| 136 | ? colored | |
| 137 | : plain; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Coerce `val`. | |
| 142 | */ | |
| 143 | ||
| 144 | 1 | function coerce(val) { |
| 145 | 0 | if (val instanceof Error) return val.stack || val.message; |
| 146 | 0 | return val; |
| 147 | } | |
| 148 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var debug = require('debug')('send') |
| 7 | , parseRange = require('range-parser') | |
| 8 | , Stream = require('stream') | |
| 9 | , mime = require('mime') | |
| 10 | , fresh = require('fresh') | |
| 11 | , path = require('path') | |
| 12 | , http = require('http') | |
| 13 | , fs = require('fs') | |
| 14 | , basename = path.basename | |
| 15 | , normalize = path.normalize | |
| 16 | , join = path.join | |
| 17 | , utils = require('./utils'); | |
| 18 | ||
| 19 | /** | |
| 20 | * Expose `send`. | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | exports = module.exports = send; |
| 24 | ||
| 25 | /** | |
| 26 | * Expose mime module. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | exports.mime = mime; |
| 30 | ||
| 31 | /** | |
| 32 | * Return a `SendStream` for `req` and `path`. | |
| 33 | * | |
| 34 | * @param {Request} req | |
| 35 | * @param {String} path | |
| 36 | * @param {Object} options | |
| 37 | * @return {SendStream} | |
| 38 | * @api public | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | function send(req, path, options) { |
| 42 | 0 | return new SendStream(req, path, options); |
| 43 | } | |
| 44 | ||
| 45 | /** | |
| 46 | * Initialize a `SendStream` with the given `path`. | |
| 47 | * | |
| 48 | * Events: | |
| 49 | * | |
| 50 | * - `error` an error occurred | |
| 51 | * - `stream` file streaming has started | |
| 52 | * - `end` streaming has completed | |
| 53 | * - `directory` a directory was requested | |
| 54 | * | |
| 55 | * @param {Request} req | |
| 56 | * @param {String} path | |
| 57 | * @param {Object} options | |
| 58 | * @api private | |
| 59 | */ | |
| 60 | ||
| 61 | 1 | function SendStream(req, path, options) { |
| 62 | 0 | var self = this; |
| 63 | 0 | this.req = req; |
| 64 | 0 | this.path = path; |
| 65 | 0 | this.options = options || {}; |
| 66 | 0 | this.maxage(0); |
| 67 | 0 | this.hidden(false); |
| 68 | 0 | this.index('index.html'); |
| 69 | } | |
| 70 | ||
| 71 | /** | |
| 72 | * Inherits from `Stream.prototype`. | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | SendStream.prototype.__proto__ = Stream.prototype; |
| 76 | ||
| 77 | /** | |
| 78 | * Enable or disable "hidden" (dot) files. | |
| 79 | * | |
| 80 | * @param {Boolean} path | |
| 81 | * @return {SendStream} | |
| 82 | * @api public | |
| 83 | */ | |
| 84 | ||
| 85 | 1 | SendStream.prototype.hidden = function(val){ |
| 86 | 0 | debug('hidden %s', val); |
| 87 | 0 | this._hidden = val; |
| 88 | 0 | return this; |
| 89 | }; | |
| 90 | ||
| 91 | /** | |
| 92 | * Set index `path`, set to a falsy | |
| 93 | * value to disable index support. | |
| 94 | * | |
| 95 | * @param {String|Boolean} path | |
| 96 | * @return {SendStream} | |
| 97 | * @api public | |
| 98 | */ | |
| 99 | ||
| 100 | 1 | SendStream.prototype.index = function(path){ |
| 101 | 0 | debug('index %s', path); |
| 102 | 0 | this._index = path; |
| 103 | 0 | return this; |
| 104 | }; | |
| 105 | ||
| 106 | /** | |
| 107 | * Set root `path`. | |
| 108 | * | |
| 109 | * @param {String} path | |
| 110 | * @return {SendStream} | |
| 111 | * @api public | |
| 112 | */ | |
| 113 | ||
| 114 | 1 | SendStream.prototype.root = |
| 115 | SendStream.prototype.from = function(path){ | |
| 116 | 0 | this._root = normalize(path); |
| 117 | 0 | return this; |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Set max-age to `ms`. | |
| 122 | * | |
| 123 | * @param {Number} ms | |
| 124 | * @return {SendStream} | |
| 125 | * @api public | |
| 126 | */ | |
| 127 | ||
| 128 | 1 | SendStream.prototype.maxage = function(ms){ |
| 129 | 0 | if (Infinity == ms) ms = 60 * 60 * 24 * 365 * 1000; |
| 130 | 0 | debug('max-age %d', ms); |
| 131 | 0 | this._maxage = ms; |
| 132 | 0 | return this; |
| 133 | }; | |
| 134 | ||
| 135 | /** | |
| 136 | * Emit error with `status`. | |
| 137 | * | |
| 138 | * @param {Number} status | |
| 139 | * @api private | |
| 140 | */ | |
| 141 | ||
| 142 | 1 | SendStream.prototype.error = function(status, err){ |
| 143 | 0 | var res = this.res; |
| 144 | 0 | var msg = http.STATUS_CODES[status]; |
| 145 | 0 | err = err || new Error(msg); |
| 146 | 0 | err.status = status; |
| 147 | 0 | if (this.listeners('error').length) return this.emit('error', err); |
| 148 | 0 | res.statusCode = err.status; |
| 149 | 0 | res.end(msg); |
| 150 | }; | |
| 151 | ||
| 152 | /** | |
| 153 | * Check if the pathname is potentially malicious. | |
| 154 | * | |
| 155 | * @return {Boolean} | |
| 156 | * @api private | |
| 157 | */ | |
| 158 | ||
| 159 | 1 | SendStream.prototype.isMalicious = function(){ |
| 160 | 0 | return !this._root && ~this.path.indexOf('..'); |
| 161 | }; | |
| 162 | ||
| 163 | /** | |
| 164 | * Check if the pathname ends with "/". | |
| 165 | * | |
| 166 | * @return {Boolean} | |
| 167 | * @api private | |
| 168 | */ | |
| 169 | ||
| 170 | 1 | SendStream.prototype.hasTrailingSlash = function(){ |
| 171 | 0 | return '/' == this.path[this.path.length - 1]; |
| 172 | }; | |
| 173 | ||
| 174 | /** | |
| 175 | * Check if the basename leads with ".". | |
| 176 | * | |
| 177 | * @return {Boolean} | |
| 178 | * @api private | |
| 179 | */ | |
| 180 | ||
| 181 | 1 | SendStream.prototype.hasLeadingDot = function(){ |
| 182 | 0 | return '.' == basename(this.path)[0]; |
| 183 | }; | |
| 184 | ||
| 185 | /** | |
| 186 | * Check if this is a conditional GET request. | |
| 187 | * | |
| 188 | * @return {Boolean} | |
| 189 | * @api private | |
| 190 | */ | |
| 191 | ||
| 192 | 1 | SendStream.prototype.isConditionalGET = function(){ |
| 193 | 0 | return this.req.headers['if-none-match'] |
| 194 | || this.req.headers['if-modified-since']; | |
| 195 | }; | |
| 196 | ||
| 197 | /** | |
| 198 | * Strip content-* header fields. | |
| 199 | * | |
| 200 | * @api private | |
| 201 | */ | |
| 202 | ||
| 203 | 1 | SendStream.prototype.removeContentHeaderFields = function(){ |
| 204 | 0 | var res = this.res; |
| 205 | 0 | Object.keys(res._headers).forEach(function(field){ |
| 206 | 0 | if (0 == field.indexOf('content')) { |
| 207 | 0 | res.removeHeader(field); |
| 208 | } | |
| 209 | }); | |
| 210 | }; | |
| 211 | ||
| 212 | /** | |
| 213 | * Respond with 304 not modified. | |
| 214 | * | |
| 215 | * @api private | |
| 216 | */ | |
| 217 | ||
| 218 | 1 | SendStream.prototype.notModified = function(){ |
| 219 | 0 | var res = this.res; |
| 220 | 0 | debug('not modified'); |
| 221 | 0 | this.removeContentHeaderFields(); |
| 222 | 0 | res.statusCode = 304; |
| 223 | 0 | res.end(); |
| 224 | }; | |
| 225 | ||
| 226 | /** | |
| 227 | * Check if the request is cacheable, aka | |
| 228 | * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}). | |
| 229 | * | |
| 230 | * @return {Boolean} | |
| 231 | * @api private | |
| 232 | */ | |
| 233 | ||
| 234 | 1 | SendStream.prototype.isCachable = function(){ |
| 235 | 0 | var res = this.res; |
| 236 | 0 | return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode; |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Handle stat() error. | |
| 241 | * | |
| 242 | * @param {Error} err | |
| 243 | * @api private | |
| 244 | */ | |
| 245 | ||
| 246 | 1 | SendStream.prototype.onStatError = function(err){ |
| 247 | 0 | var notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']; |
| 248 | 0 | if (~notfound.indexOf(err.code)) return this.error(404, err); |
| 249 | 0 | this.error(500, err); |
| 250 | }; | |
| 251 | ||
| 252 | /** | |
| 253 | * Check if the cache is fresh. | |
| 254 | * | |
| 255 | * @return {Boolean} | |
| 256 | * @api private | |
| 257 | */ | |
| 258 | ||
| 259 | 1 | SendStream.prototype.isFresh = function(){ |
| 260 | 0 | return fresh(this.req.headers, this.res._headers); |
| 261 | }; | |
| 262 | ||
| 263 | /** | |
| 264 | * Redirect to `path`. | |
| 265 | * | |
| 266 | * @param {String} path | |
| 267 | * @api private | |
| 268 | */ | |
| 269 | ||
| 270 | 1 | SendStream.prototype.redirect = function(path){ |
| 271 | 0 | if (this.listeners('directory').length) return this.emit('directory'); |
| 272 | 0 | var res = this.res; |
| 273 | 0 | path += '/'; |
| 274 | 0 | res.statusCode = 301; |
| 275 | 0 | res.setHeader('Location', path); |
| 276 | 0 | res.end('Redirecting to ' + utils.escape(path)); |
| 277 | }; | |
| 278 | ||
| 279 | /** | |
| 280 | * Pipe to `res. | |
| 281 | * | |
| 282 | * @param {Stream} res | |
| 283 | * @return {Stream} res | |
| 284 | * @api public | |
| 285 | */ | |
| 286 | ||
| 287 | 1 | SendStream.prototype.pipe = function(res){ |
| 288 | 0 | var self = this |
| 289 | , args = arguments | |
| 290 | , path = this.path | |
| 291 | , root = this._root; | |
| 292 | ||
| 293 | // references | |
| 294 | 0 | this.res = res; |
| 295 | ||
| 296 | // invalid request uri | |
| 297 | 0 | path = utils.decode(path); |
| 298 | 0 | if (-1 == path) return this.error(400); |
| 299 | ||
| 300 | // null byte(s) | |
| 301 | 0 | if (~path.indexOf('\0')) return this.error(400); |
| 302 | ||
| 303 | // join / normalize from optional root dir | |
| 304 | 0 | if (root) path = normalize(join(this._root, path)); |
| 305 | ||
| 306 | // ".." is malicious without "root" | |
| 307 | 0 | if (this.isMalicious()) return this.error(403); |
| 308 | ||
| 309 | // malicious path | |
| 310 | 0 | if (root && 0 != path.indexOf(root)) return this.error(403); |
| 311 | ||
| 312 | // hidden file support | |
| 313 | 0 | if (!this._hidden && this.hasLeadingDot()) return this.error(404); |
| 314 | ||
| 315 | // index file support | |
| 316 | 0 | if (this._index && this.hasTrailingSlash()) path += this._index; |
| 317 | ||
| 318 | 0 | debug('stat "%s"', path); |
| 319 | 0 | fs.stat(path, function(err, stat){ |
| 320 | 0 | if (err) return self.onStatError(err); |
| 321 | 0 | if (stat.isDirectory()) return self.redirect(self.path); |
| 322 | 0 | self.emit('file', path, stat); |
| 323 | 0 | self.send(path, stat); |
| 324 | }); | |
| 325 | ||
| 326 | 0 | return res; |
| 327 | }; | |
| 328 | ||
| 329 | /** | |
| 330 | * Transfer `path`. | |
| 331 | * | |
| 332 | * @param {String} path | |
| 333 | * @api public | |
| 334 | */ | |
| 335 | ||
| 336 | 1 | SendStream.prototype.send = function(path, stat){ |
| 337 | 0 | var options = this.options; |
| 338 | 0 | var len = stat.size; |
| 339 | 0 | var res = this.res; |
| 340 | 0 | var req = this.req; |
| 341 | 0 | var ranges = req.headers.range; |
| 342 | 0 | var offset = options.start || 0; |
| 343 | ||
| 344 | // set header fields | |
| 345 | 0 | this.setHeader(stat); |
| 346 | ||
| 347 | // set content-type | |
| 348 | 0 | this.type(path); |
| 349 | ||
| 350 | // conditional GET support | |
| 351 | 0 | if (this.isConditionalGET() |
| 352 | && this.isCachable() | |
| 353 | && this.isFresh()) { | |
| 354 | 0 | return this.notModified(); |
| 355 | } | |
| 356 | ||
| 357 | // adjust len to start/end options | |
| 358 | 0 | len = Math.max(0, len - offset); |
| 359 | 0 | if (options.end !== undefined) { |
| 360 | 0 | var bytes = options.end - offset + 1; |
| 361 | 0 | if (len > bytes) len = bytes; |
| 362 | } | |
| 363 | ||
| 364 | // Range support | |
| 365 | 0 | if (ranges) { |
| 366 | 0 | ranges = parseRange(len, ranges); |
| 367 | ||
| 368 | // unsatisfiable | |
| 369 | 0 | if (-1 == ranges) { |
| 370 | 0 | res.setHeader('Content-Range', 'bytes */' + stat.size); |
| 371 | 0 | return this.error(416); |
| 372 | } | |
| 373 | ||
| 374 | // valid (syntactically invalid ranges are treated as a regular response) | |
| 375 | 0 | if (-2 != ranges) { |
| 376 | 0 | options.start = offset + ranges[0].start; |
| 377 | 0 | options.end = offset + ranges[0].end; |
| 378 | ||
| 379 | // Content-Range | |
| 380 | 0 | res.statusCode = 206; |
| 381 | 0 | res.setHeader('Content-Range', 'bytes ' |
| 382 | + ranges[0].start | |
| 383 | + '-' | |
| 384 | + ranges[0].end | |
| 385 | + '/' | |
| 386 | + len); | |
| 387 | 0 | len = options.end - options.start + 1; |
| 388 | } | |
| 389 | } | |
| 390 | ||
| 391 | // content-length | |
| 392 | 0 | res.setHeader('Content-Length', len); |
| 393 | ||
| 394 | // HEAD support | |
| 395 | 0 | if ('HEAD' == req.method) return res.end(); |
| 396 | ||
| 397 | 0 | this.stream(path, options); |
| 398 | }; | |
| 399 | ||
| 400 | /** | |
| 401 | * Stream `path` to the response. | |
| 402 | * | |
| 403 | * @param {String} path | |
| 404 | * @param {Object} options | |
| 405 | * @api private | |
| 406 | */ | |
| 407 | ||
| 408 | 1 | SendStream.prototype.stream = function(path, options){ |
| 409 | // TODO: this is all lame, refactor meeee | |
| 410 | 0 | var self = this; |
| 411 | 0 | var res = this.res; |
| 412 | 0 | var req = this.req; |
| 413 | ||
| 414 | // pipe | |
| 415 | 0 | var stream = fs.createReadStream(path, options); |
| 416 | 0 | this.emit('stream', stream); |
| 417 | 0 | stream.pipe(res); |
| 418 | ||
| 419 | // socket closed, done with the fd | |
| 420 | 0 | req.on('close', stream.destroy.bind(stream)); |
| 421 | ||
| 422 | // error handling code-smell | |
| 423 | 0 | stream.on('error', function(err){ |
| 424 | // no hope in responding | |
| 425 | 0 | if (res._header) { |
| 426 | 0 | console.error(err.stack); |
| 427 | 0 | req.destroy(); |
| 428 | 0 | return; |
| 429 | } | |
| 430 | ||
| 431 | // 500 | |
| 432 | 0 | err.status = 500; |
| 433 | 0 | self.emit('error', err); |
| 434 | }); | |
| 435 | ||
| 436 | // end | |
| 437 | 0 | stream.on('end', function(){ |
| 438 | 0 | self.emit('end'); |
| 439 | }); | |
| 440 | }; | |
| 441 | ||
| 442 | /** | |
| 443 | * Set content-type based on `path` | |
| 444 | * if it hasn't been explicitly set. | |
| 445 | * | |
| 446 | * @param {String} path | |
| 447 | * @api private | |
| 448 | */ | |
| 449 | ||
| 450 | 1 | SendStream.prototype.type = function(path){ |
| 451 | 0 | var res = this.res; |
| 452 | 0 | if (res.getHeader('Content-Type')) return; |
| 453 | 0 | var type = mime.lookup(path); |
| 454 | 0 | var charset = mime.charsets.lookup(type); |
| 455 | 0 | debug('content-type %s', type); |
| 456 | 0 | res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); |
| 457 | }; | |
| 458 | ||
| 459 | /** | |
| 460 | * Set reaponse header fields, most | |
| 461 | * fields may be pre-defined. | |
| 462 | * | |
| 463 | * @param {Object} stat | |
| 464 | * @api private | |
| 465 | */ | |
| 466 | ||
| 467 | 1 | SendStream.prototype.setHeader = function(stat){ |
| 468 | 0 | var res = this.res; |
| 469 | 0 | if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes'); |
| 470 | 0 | if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat)); |
| 471 | 0 | if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString()); |
| 472 | 0 | if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (this._maxage / 1000)); |
| 473 | 0 | if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString()); |
| 474 | }; | |
| 475 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Return an ETag in the form of `"<size>-<mtime>"` | |
| 4 | * from the given `stat`. | |
| 5 | * | |
| 6 | * @param {Object} stat | |
| 7 | * @return {String} | |
| 8 | * @api private | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | exports.etag = function(stat) { |
| 12 | 0 | return '"' + stat.size + '-' + Number(stat.mtime) + '"'; |
| 13 | }; | |
| 14 | ||
| 15 | /** | |
| 16 | * decodeURIComponent. | |
| 17 | * | |
| 18 | * Allows V8 to only deoptimize this fn instead of all | |
| 19 | * of send(). | |
| 20 | * | |
| 21 | * @param {String} path | |
| 22 | * @api private | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | exports.decode = function(path){ |
| 26 | 0 | try { |
| 27 | 0 | return decodeURIComponent(path); |
| 28 | } catch (err) { | |
| 29 | 0 | return -1; |
| 30 | } | |
| 31 | }; | |
| 32 | ||
| 33 | /** | |
| 34 | * Escape the given string of `html`. | |
| 35 | * | |
| 36 | * @param {String} html | |
| 37 | * @return {String} | |
| 38 | * @api private | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | exports.escape = function(html){ |
| 42 | 0 | return String(html) |
| 43 | .replace(/&(?!\w+;)/g, '&') | |
| 44 | .replace(/</g, '<') | |
| 45 | .replace(/>/g, '>') | |
| 46 | .replace(/"/g, '"'); | |
| 47 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | /*jshint maxstatements:35, maxcomplexity:10*/ | |
| 19 | 1 | 'use strict'; |
| 20 | ||
| 21 | 1 | var Q = require('q'), |
| 22 | tls = require('tls'), | |
| 23 | express = require('express'), | |
| 24 | enrouten = require('express-enrouten'), | |
| 25 | patch = require('./patch'), | |
| 26 | kraken = require('./middleware'), | |
| 27 | util = require('./util'), | |
| 28 | pathutil = util.pathutil, | |
| 29 | config = util.configutil; | |
| 30 | ||
| 31 | ||
| 32 | 1 | var proto = { |
| 33 | ||
| 34 | init: function (callback) { | |
| 35 | 0 | this._configure(callback); |
| 36 | }, | |
| 37 | ||
| 38 | ||
| 39 | _configure: function (callback) { | |
| 40 | 0 | var self, app; |
| 41 | ||
| 42 | 0 | self = this; |
| 43 | 0 | app = this._app; |
| 44 | ||
| 45 | 0 | function next(err) { |
| 46 | 0 | var config, settings; |
| 47 | ||
| 48 | 0 | if (err) { |
| 49 | 0 | callback(err); |
| 50 | 0 | return; |
| 51 | } | |
| 52 | ||
| 53 | 0 | config = self._config; |
| 54 | 0 | patch.apply('config', app, config); |
| 55 | ||
| 56 | // XXX: Special-case resolving `express:views` until we get config protocols working. | |
| 57 | 0 | config.set('express:views', self._resolve(config.get('express:views'))); |
| 58 | 0 | config.set('express:env', config.get('env:env')); |
| 59 | 0 | config.set('express:port', config.port); |
| 60 | 0 | config.set('express:host', config.host); |
| 61 | ||
| 62 | 0 | settings = config.get('express'); |
| 63 | 0 | Object.keys(settings).forEach(function (key) { |
| 64 | 0 | app.set(key, settings[key]); |
| 65 | }); | |
| 66 | ||
| 67 | 0 | settings = config.get('ssl'); |
| 68 | ||
| 69 | 0 | if (settings) { |
| 70 | 0 | app.set('ssl', settings); |
| 71 | 0 | tls.SLAB_BUFFER_SIZE = settings.slabBufferSize || tls.SLAB_BUFFER_SIZE; |
| 72 | 0 | tls.CLIENT_RENEG_LIMIT = settings.clientRenegotiationLimit || tls.CLIENT_RENEG_LIMIT; |
| 73 | 0 | tls.CLIENT_RENEG_WINDOW = settings.clientRenegotiationWindow || tls.CLIENT_RENEG_WINDOW; |
| 74 | } | |
| 75 | ||
| 76 | 0 | app.get('view engine') && self._views(); |
| 77 | 0 | self._middleware(); |
| 78 | 0 | callback(); |
| 79 | } | |
| 80 | ||
| 81 | ||
| 82 | 0 | this._config = config.create(this._resolve('.')); |
| 83 | ||
| 84 | 0 | if (typeof this._delegate.configure === 'function') { |
| 85 | 0 | this._delegate.configure(this._config.raw, next); |
| 86 | 0 | return; |
| 87 | } | |
| 88 | ||
| 89 | 0 | next(); |
| 90 | }, | |
| 91 | ||
| 92 | ||
| 93 | _views: function () { | |
| 94 | 0 | var app, config, i18n, cache, engines, module; |
| 95 | ||
| 96 | /** | |
| 97 | * XXXXX ACHTUNG! ALERT! ALERT! PELIGRO! XXXXX | |
| 98 | * The following code is brittle and needs refactoring. It has already been the | |
| 99 | * source of many bugs and smells terrible. Consider getting kraken out of the | |
| 100 | * i18n game altogether. | |
| 101 | */ | |
| 102 | ||
| 103 | 0 | app = this._app; |
| 104 | 0 | config = this._config; |
| 105 | ||
| 106 | // If i18n is enabled (config is available), set its cache | |
| 107 | // to the view cache value, and disable the view cache. | |
| 108 | 0 | i18n = config.get('i18n'); |
| 109 | 0 | cache = config.get('express:view cache'); |
| 110 | 0 | if (i18n) { |
| 111 | // Set i18n to the view engine cache settings. If the view | |
| 112 | // engine cache is enabled, disable it so i18n can take over. | |
| 113 | // Either way caching is still enabled, so renderer caches will | |
| 114 | // still be disabled below. | |
| 115 | 0 | i18n.cache = cache; |
| 116 | 0 | if (cache) { |
| 117 | 0 | app.set('view cache', false); |
| 118 | 0 | config.set('express:view cache', false); |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | ||
| 123 | // Register each rendering engine | |
| 124 | 0 | engines = config.get('view engines'); |
| 125 | 0 | Object.keys(engines).forEach(function (ext) { |
| 126 | 0 | var meta, engine, renderer; |
| 127 | ||
| 128 | 0 | meta = engines[ext]; |
| 129 | 0 | engine = require(meta.module); |
| 130 | 0 | renderer = engine[ext]; |
| 131 | ||
| 132 | // Assume a single argument renderer means it's actually a factory | |
| 133 | // method and needs to be configured. | |
| 134 | 0 | if (typeof renderer === 'function' && renderer.length === 1) { |
| 135 | // Now create the real renderer. If express cache is enabled | |
| 136 | // favor that over view engine cache. | |
| 137 | 0 | meta.settings = meta.settings || {}; |
| 138 | 0 | meta.settings.cache = (cache === true) ? false : meta.settings.cache; |
| 139 | 0 | renderer = renderer(meta.settings); |
| 140 | } | |
| 141 | ||
| 142 | 0 | app.engine(ext, renderer); |
| 143 | }); | |
| 144 | ||
| 145 | 0 | if (i18n) { |
| 146 | // After all engines are registered, apply i18n. It needs to know | |
| 147 | // about the state of the current view engine in order to work. :/ | |
| 148 | 0 | module = util.tryRequire('makara'); |
| 149 | 0 | i18n.contentPath = this._resolve(i18n.contentPath); |
| 150 | 0 | this._i18n = module && module.create(app, i18n); |
| 151 | } | |
| 152 | ||
| 153 | }, | |
| 154 | ||
| 155 | ||
| 156 | _middleware: function () { | |
| 157 | 0 | var app, delegate, config, srcRoot, staticRoot, errorPages; |
| 158 | ||
| 159 | 0 | app = this._app; |
| 160 | 0 | delegate = this._delegate; |
| 161 | 0 | config = this._config.get('middleware'); |
| 162 | 0 | srcRoot = this._resolve(config.static.srcRoot); |
| 163 | 0 | staticRoot = this._resolve(config.static.rootPath); |
| 164 | 0 | errorPages = config.errorPages || {}; |
| 165 | ||
| 166 | 0 | app.use(kraken.shutdown(app, this._config, errorPages['503'])); |
| 167 | 0 | app.use(express.favicon()); |
| 168 | 0 | app.use(kraken.compiler(srcRoot, staticRoot, this._config, this._i18n)); |
| 169 | 0 | app.use(express.static(staticRoot)); |
| 170 | 0 | app.use(kraken.logger(config.logger)); |
| 171 | ||
| 172 | 0 | if (typeof delegate.requestStart === 'function') { |
| 173 | 0 | delegate.requestStart(app); |
| 174 | } | |
| 175 | ||
| 176 | 0 | app.use(express.json(config.json)); |
| 177 | 0 | app.use(express.urlencoded(config.urlencoded)); |
| 178 | ||
| 179 | 0 | if (config.multipart) { |
| 180 | 0 | app.use(kraken.multipart(config.multipart.params)); |
| 181 | } | |
| 182 | ||
| 183 | 0 | app.use(express.cookieParser(config.session.secret)); |
| 184 | 0 | app.use(kraken.session(config.session)); |
| 185 | 0 | app.use(kraken.appsec(config.appsec)); |
| 186 | ||
| 187 | 0 | if (typeof delegate.requestBeforeRoute === 'function') { |
| 188 | 0 | delegate.requestBeforeRoute(app); |
| 189 | } | |
| 190 | ||
| 191 | 0 | enrouten(app).withRoutes({ |
| 192 | directory: this._resolve(this._config.get('routes:routePath')) | |
| 193 | }); | |
| 194 | ||
| 195 | 0 | if (typeof delegate.requestAfterRoute === 'function') { |
| 196 | 0 | delegate.requestAfterRoute(app); |
| 197 | } | |
| 198 | ||
| 199 | 0 | app.use(kraken.fileNotFound(errorPages['404'])); |
| 200 | 0 | app.use(kraken.serverError(errorPages['500'])); |
| 201 | 0 | app.use(kraken.errorHandler(config.errorHandler)); |
| 202 | }, | |
| 203 | ||
| 204 | _resolve: function (path) { | |
| 205 | 0 | return this._resolver.resolve(path); |
| 206 | } | |
| 207 | }; | |
| 208 | ||
| 209 | ||
| 210 | 1 | function create(delegate, resolver, callback) { |
| 211 | 0 | var app, appcore; |
| 212 | ||
| 213 | 0 | if (isExpress(delegate)) { |
| 214 | 0 | callback(null, delegate); |
| 215 | 0 | return; |
| 216 | } | |
| 217 | ||
| 218 | 0 | if (typeof resolver === 'function') { |
| 219 | 0 | callback = resolver; |
| 220 | 0 | resolver = pathutil.create(); |
| 221 | } | |
| 222 | ||
| 223 | 0 | app = express(); |
| 224 | 0 | if (!delegate) { |
| 225 | 0 | patch.apply('stream', app); |
| 226 | 0 | callback(null, app); |
| 227 | 0 | return; |
| 228 | } | |
| 229 | ||
| 230 | 0 | appcore = Object.create(proto, { |
| 231 | _app: { | |
| 232 | enumerable: true, | |
| 233 | writable: false, | |
| 234 | value: app | |
| 235 | }, | |
| 236 | _delegate: { | |
| 237 | enumerable: true, | |
| 238 | writable: false, | |
| 239 | value: delegate | |
| 240 | }, | |
| 241 | _resolver: { | |
| 242 | enumerable: true, | |
| 243 | writable: false, | |
| 244 | value: resolver | |
| 245 | }, | |
| 246 | _config: { | |
| 247 | enumerable: true, | |
| 248 | writable: true, | |
| 249 | value: undefined | |
| 250 | }, | |
| 251 | _i18n: { | |
| 252 | enumerable: true, | |
| 253 | writable: true, | |
| 254 | value: undefined | |
| 255 | } | |
| 256 | }); | |
| 257 | ||
| 258 | 0 | appcore.init(function (err) { |
| 259 | 0 | if (err) { |
| 260 | 0 | callback(err); |
| 261 | 0 | return; |
| 262 | } | |
| 263 | 0 | callback(null, app); |
| 264 | }); | |
| 265 | } | |
| 266 | ||
| 267 | ||
| 268 | 1 | function isExpress(obj) { |
| 269 | 0 | return typeof obj === 'function' && obj.handle && obj.set; |
| 270 | } | |
| 271 | ||
| 272 | ||
| 273 | 1 | exports.create = function (delegate, resolver, callback) { |
| 274 | 0 | if (!callback) { |
| 275 | 0 | return Q.nfbind(create)(delegate, resolver); |
| 276 | } | |
| 277 | ||
| 278 | 0 | setImmediate(create.bind(null, delegate, resolver, callback)); |
| 279 | }; | |
| 280 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var fs = require('fs'), |
| 21 | path = require('path'), | |
| 22 | rimraf = require('rimraf'), | |
| 23 | mkdirp = require('mkdirp'), | |
| 24 | devtools = require('kraken-devtools'), | |
| 25 | util = require('../util'); | |
| 26 | ||
| 27 | ||
| 28 | 1 | module.exports = function (srcRoot, destRoot, config, intl) { |
| 29 | 0 | var i18n, dust, options, directory; |
| 30 | ||
| 31 | 0 | i18n = config.get('i18n'); |
| 32 | 0 | dust = util.tryRequire('dustjs-linkedin'); |
| 33 | 0 | options = config.get('middleware:compiler'); |
| 34 | 0 | directory = options && options.dust; |
| 35 | ||
| 36 | ||
| 37 | 0 | if (i18n && util.tryRequire('makara') && dust && directory) { |
| 38 | ||
| 39 | 0 | options.dust = { |
| 40 | ||
| 41 | dir: directory, | |
| 42 | ||
| 43 | precompile: function (context, callback) { | |
| 44 | 0 | var locality, locale, dest; |
| 45 | ||
| 46 | // Look for Country/Locale tuple in path | |
| 47 | 0 | locale = context.name.match(/(?:([A-Za-z]{2})\/([A-Za-z]{2})\/)?(.*)/); |
| 48 | 0 | if (locale && locale[1] && locale[2]) { |
| 49 | 0 | locality = { |
| 50 | country: locale[1], | |
| 51 | language: locale[2] | |
| 52 | }; | |
| 53 | } | |
| 54 | ||
| 55 | 0 | if (locale[3]) { |
| 56 | 0 | context.name = locale[3]; |
| 57 | } | |
| 58 | ||
| 59 | // Store original source root and switch it so a tmp directory. | |
| 60 | 0 | context.origSrcRoot = context.srcRoot; |
| 61 | 0 | context.srcRoot = path.join(context.srcRoot, 'tmp'); |
| 62 | ||
| 63 | // Determine the interim tempfile (a preprocessed dust file) | |
| 64 | 0 | dest = path.join(context.srcRoot, context.filePath); |
| 65 | 0 | dest = dest.replace(path.extname(dest), '') + '.dust'; |
| 66 | ||
| 67 | 0 | intl.localize(context.name, locality, function (err, data) { |
| 68 | 0 | if (err) { |
| 69 | 0 | callback(err); |
| 70 | 0 | return; |
| 71 | } | |
| 72 | ||
| 73 | 0 | mkdirp(path.dirname(dest), function (err) { |
| 74 | 0 | if (err) { |
| 75 | 0 | callback(err); |
| 76 | 0 | return; |
| 77 | } | |
| 78 | 0 | fs.writeFile(dest, data, function (err) { |
| 79 | 0 | callback(err, context); |
| 80 | }); | |
| 81 | }); | |
| 82 | ||
| 83 | }); | |
| 84 | ||
| 85 | }, | |
| 86 | ||
| 87 | postcompile: function (context, callback) { | |
| 88 | // Remove temp files | |
| 89 | 0 | rimraf(context.srcRoot, callback); |
| 90 | } | |
| 91 | ||
| 92 | }; | |
| 93 | } | |
| 94 | ||
| 95 | 0 | return devtools.compiler(srcRoot, destRoot, options); |
| 96 | }; | |
| 97 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var express = require('express'), |
| 21 | util = require('../util'); | |
| 22 | ||
| 23 | ||
| 24 | 1 | exports.defaultHandler = function (settings) { |
| 25 | 0 | var handler = util.tryRequire(settings && settings.module, express.errorHandler); |
| 26 | 0 | return handler(settings); |
| 27 | }; | |
| 28 | ||
| 29 | 1 | exports.fileNotFound = function (template) { |
| 30 | 0 | return function (req, res, next) { |
| 31 | 0 | var model = { url: req.url }; |
| 32 | ||
| 33 | 0 | if (template) { |
| 34 | 0 | if (req.xhr) { |
| 35 | 0 | res.send(404, model); |
| 36 | } else { | |
| 37 | 0 | res.status(404); |
| 38 | 0 | res.render(template, model); |
| 39 | } | |
| 40 | } else { | |
| 41 | 0 | next(); |
| 42 | } | |
| 43 | }; | |
| 44 | }; | |
| 45 | ||
| 46 | ||
| 47 | 1 | exports.serverError = function (template) { |
| 48 | 0 | return function (err, req, res, next) { |
| 49 | 0 | var model = { url: req.url, err: err }; |
| 50 | ||
| 51 | 0 | console.error('Error:' + err.message); |
| 52 | ||
| 53 | 0 | if (template) { |
| 54 | 0 | if (req.xhr) { |
| 55 | 0 | res.send(500, model); |
| 56 | } else { | |
| 57 | 0 | res.status(500); |
| 58 | 0 | res.render(template, model); |
| 59 | } | |
| 60 | } else { | |
| 61 | 0 | next(err); |
| 62 | } | |
| 63 | }; | |
| 64 | }; | |
| 65 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var error = require('./error'); |
| 21 | ||
| 22 | ||
| 23 | 1 | exports = module.exports = { |
| 24 | ||
| 25 | logger: require('./logger'), | |
| 26 | ||
| 27 | session: require('./session'), | |
| 28 | ||
| 29 | appsec: require('lusca'), | |
| 30 | ||
| 31 | multipart: require('./multipart'), | |
| 32 | ||
| 33 | shutdown: require('./shutdown'), | |
| 34 | ||
| 35 | compiler: require('./compiler'), | |
| 36 | ||
| 37 | fileNotFound: error.fileNotFound, | |
| 38 | ||
| 39 | serverError: error.serverError, | |
| 40 | ||
| 41 | errorHandler: error.defaultHandler | |
| 42 | ||
| 43 | }; | |
| 44 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var express = require('express'), |
| 21 | util = require('../util'); | |
| 22 | ||
| 23 | ||
| 24 | // NOTE: It's possible for clients to define middleware by name or fall back to a default as defined | |
| 25 | // by kraken. This takes advantage of node module lookup rules, thus will use the module | |
| 26 | // available in the parent module as it won't be available in the current module. | |
| 27 | 1 | module.exports = function (settings) { |
| 28 | 0 | var override = util.tryRequire(settings && settings.module, express.logger); |
| 29 | 0 | if (typeof override === 'object' && typeof override.logger === 'function') { |
| 30 | 0 | override = override.logger; |
| 31 | } | |
| 32 | 0 | return override(settings); |
| 33 | }; | |
| 34 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var formidable = require('formidable'), |
| 21 | fs = require('fs'); | |
| 22 | ||
| 23 | 1 | module.exports = function (settings) { |
| 24 | 0 | settings = typeof settings === 'object' ? settings : {}; |
| 25 | ||
| 26 | 0 | return function (req, res, next) { |
| 27 | 0 | var form = new formidable.IncomingForm(settings), contentType = req.headers['content-type']; |
| 28 | ||
| 29 | 0 | if (typeof contentType === 'string' && contentType.indexOf('multipart/form-data') > -1) { |
| 30 | 0 | form.parse(req, function (err, fields, files) { |
| 31 | 0 | if (err) { |
| 32 | 0 | next(err); |
| 33 | 0 | return; |
| 34 | } | |
| 35 | 0 | req.body = fields; // pass along form fields |
| 36 | 0 | req.files = files; // pass along files |
| 37 | ||
| 38 | // remove tmp files after request finishes | |
| 39 | 0 | var cleanup = function () { |
| 40 | ||
| 41 | // remove tmp file(s) | |
| 42 | 0 | Object.keys(files).forEach(function (file) { |
| 43 | 0 | var filePath = files[file].path; |
| 44 | 0 | if (typeof filePath === 'string') { |
| 45 | 0 | fs.exists(filePath, function (exists) { |
| 46 | 0 | if (exists) { |
| 47 | 0 | fs.unlink(filePath, function (err) { |
| 48 | 0 | if (err) { |
| 49 | 0 | console.error('Kraken failed to remove tmp file: ' + filePath); |
| 50 | 0 | console.error(err); |
| 51 | } | |
| 52 | }); | |
| 53 | } | |
| 54 | }); | |
| 55 | } | |
| 56 | }); | |
| 57 | }; | |
| 58 | 0 | res.once('finish', cleanup); |
| 59 | 0 | res.once('close', cleanup); |
| 60 | 0 | next(); |
| 61 | }); | |
| 62 | } | |
| 63 | else { | |
| 64 | 0 | next(); |
| 65 | } | |
| 66 | }; | |
| 67 | ||
| 68 | }; | |
| 69 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var express = require('express'), |
| 21 | util = require('../util'); | |
| 22 | ||
| 23 | ||
| 24 | 1 | module.exports = function (settings) { |
| 25 | 0 | var override, Store; |
| 26 | ||
| 27 | 0 | override = util.tryRequire(settings && settings.module); |
| 28 | 0 | if (override) { |
| 29 | 0 | Store = override(express); |
| 30 | 0 | settings.store = new Store(settings.config); |
| 31 | } | |
| 32 | ||
| 33 | 0 | return express.session(settings); |
| 34 | }; | |
| 35 |
| Line | Hits | Source |
|---|---|---|
| 1 | /***@@@ BEGIN LICENSE @@@***/ | |
| 2 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 3 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 4 | │ │ | |
| 5 | │hh ,'""`. │ | |
| 6 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 7 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 8 | │ ) __ ( You may obtain a copy of the License at │ | |
| 9 | │ /,'))((`.\ │ | |
| 10 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 11 | │ `\ `)(' /' │ | |
| 12 | │ │ | |
| 13 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 14 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 15 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 16 | │ See the License for the specific language governing permissions and │ | |
| 17 | │ limitations under the License. │ | |
| 18 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 19 | /***@@@ END LICENSE @@@***/ | |
| 20 | 1 | 'use strict'; |
| 21 | ||
| 22 | 1 | module.exports = function (app, config, template) { |
| 23 | ||
| 24 | 0 | var gracefulShutdown = function () { |
| 25 | 0 | var server, timeout; |
| 26 | 0 | config.set('kraken:state', 'disconnecting'); |
| 27 | ||
| 28 | 0 | server = app.get('kraken:server'); |
| 29 | // switch to kraken.close once it's implemented | |
| 30 | 0 | server.close(function () { |
| 31 | 0 | process.exit(); |
| 32 | }); | |
| 33 | ||
| 34 | 0 | timeout = config.get('shutdownTimeout'); |
| 35 | 0 | setTimeout(function () { |
| 36 | 0 | process.exit(1); |
| 37 | }, timeout); | |
| 38 | }; | |
| 39 | ||
| 40 | 0 | process.on('SIGTERM', gracefulShutdown); |
| 41 | 0 | process.on('SIGINT', gracefulShutdown); |
| 42 | ||
| 43 | 0 | return function (req, res, next) { |
| 44 | 0 | if (config.get('kraken:state') !== 'disconnecting') { |
| 45 | 0 | return next(); |
| 46 | } | |
| 47 | 0 | res.setHeader('Connection', 'close'); |
| 48 | 0 | if (template && !req.xhr) { |
| 49 | 0 | res.status(503); |
| 50 | 0 | res.render(template); |
| 51 | } else { | |
| 52 | 0 | res.send(503, 'Server is in the process of shutting down.'); |
| 53 | } | |
| 54 | }; | |
| 55 | ||
| 56 | }; | |
| 57 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var assert = require('assert'); |
| 21 | ||
| 22 | /** | |
| 23 | * Contains all the logic to map old config settings to new | |
| 24 | * settings. When we no longer want to support old config, | |
| 25 | * this can go away. | |
| 26 | * @param app | |
| 27 | * @param config | |
| 28 | */ | |
| 29 | 1 | exports.apply = function (app, config) { |
| 30 | 0 | var views, engine, module, engines, existing, proxy; |
| 31 | ||
| 32 | // Map old viewEngine config to new | |
| 33 | 0 | views = config.get('viewEngine'); |
| 34 | 0 | if (views) { |
| 35 | ||
| 36 | 0 | console.warn('`viewEngine` configuration is deprecated. Please see documentation for details.'); |
| 37 | ||
| 38 | 0 | if (views.cache !== undefined) { |
| 39 | 0 | config.set('express:view cache', views.cache); |
| 40 | 0 | delete views.cache; |
| 41 | } | |
| 42 | ||
| 43 | 0 | if (views.templatePath !== undefined) { |
| 44 | 0 | config.set('express:views', views.templatePath); |
| 45 | 0 | delete views.templatePath; |
| 46 | } | |
| 47 | ||
| 48 | // There should ALWAYS be an engine, either in old or new config. | |
| 49 | 0 | engine = views.ext || config.get('express:view engine'); |
| 50 | 0 | assert(engine, 'No view engine configured.'); |
| 51 | 0 | config.set('express:view engine', engine); |
| 52 | 0 | delete views.ext; |
| 53 | ||
| 54 | 0 | module = views.module; |
| 55 | 0 | delete views.module; |
| 56 | ||
| 57 | // Ok, if an engine is defined first check the new config style to | |
| 58 | // see if we need to overwrite/merge `module` and `settings` or create | |
| 59 | // a new entry altogether. | |
| 60 | 0 | existing = config.get('view engines:' + engine) || { |
| 61 | module: undefined, | |
| 62 | settings: {} | |
| 63 | }; | |
| 64 | ||
| 65 | // No module was defined, so don't overwrite. Then update the engine config, | |
| 66 | // and finally set the settings directly so the values of `views` get merged | |
| 67 | // into the object instead of overwritten. | |
| 68 | 0 | module && (existing.module = module); |
| 69 | 0 | config.set('view engines:' + engine, existing); |
| 70 | 0 | config.set('view engines:' + engine + ':settings', views); |
| 71 | 0 | config.remove('viewEngine'); |
| 72 | } | |
| 73 | ||
| 74 | // Map old proxy config to new | |
| 75 | 0 | proxy = config.get('proxy:trust'); |
| 76 | 0 | if (proxy !== undefined) { |
| 77 | 0 | config.set('express:trust proxy', config.get('proxy:trust')); |
| 78 | } | |
| 79 | }; | |
| 80 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var patches = { |
| 21 | stream: require('./stream'), | |
| 22 | config: require('./config') | |
| 23 | }; | |
| 24 | ||
| 25 | /** | |
| 26 | * This feature add the ability to apply names patches to express. Usually, | |
| 27 | * this practice is risky as it has dependencies on express internals, thus | |
| 28 | * is separate from core application code. | |
| 29 | */ | |
| 30 | 1 | exports.apply = function (names, app, config) { |
| 31 | ||
| 32 | 0 | names = names.split(/\s*,\s*/); |
| 33 | 0 | names.forEach(function (name) { |
| 34 | 0 | var patch; |
| 35 | ||
| 36 | 0 | if (!(name in patches)) { |
| 37 | 0 | throw new Error('Patch ' + name + ' not found.'); |
| 38 | } | |
| 39 | ||
| 40 | 0 | patch = patches[name]; |
| 41 | 0 | patch.apply(app, config); |
| 42 | }); | |
| 43 | ||
| 44 | }; | |
| 45 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | /*jshint proto:true*/ | |
| 19 | 1 | 'use strict'; |
| 20 | ||
| 21 | 1 | var Stream = require('stream'); |
| 22 | ||
| 23 | /** | |
| 24 | * This express patch allows rendering engines to return a stream | |
| 25 | * instead of a string to pipe responses to the client on render. | |
| 26 | * @param app and express application. | |
| 27 | */ | |
| 28 | 1 | exports.apply = function (app) { |
| 29 | ||
| 30 | 0 | app.response = { |
| 31 | ||
| 32 | __proto__: app.response, | |
| 33 | ||
| 34 | super_: app.response, | |
| 35 | ||
| 36 | render: function (view, options, fn) { | |
| 37 | 0 | var self = this; |
| 38 | ||
| 39 | 0 | if (typeof options === 'function') { |
| 40 | 0 | fn = options; |
| 41 | 0 | options = {}; |
| 42 | } | |
| 43 | ||
| 44 | 0 | fn = fn || function (err, str) { |
| 45 | 0 | if (err) { |
| 46 | 0 | self.req.next(err); |
| 47 | 0 | return; |
| 48 | } | |
| 49 | ||
| 50 | 0 | if (str instanceof Stream) { |
| 51 | // If content length was somehow added, remove it. Not applicable. | |
| 52 | 0 | self.removeHeader('Content-Length'); |
| 53 | ||
| 54 | // Set appropriate headers for this payload. | |
| 55 | // TODO: ETags? Don't really see how, but who knows. | |
| 56 | 0 | self.set('Transfer-Encoding', 'chunked'); |
| 57 | 0 | if (!self.get('Content-Type')) { |
| 58 | 0 | self.charset = self.charset || 'utf-8'; |
| 59 | 0 | self.type('html'); |
| 60 | } | |
| 61 | ||
| 62 | 0 | str.pipe(self); |
| 63 | 0 | return; |
| 64 | } | |
| 65 | ||
| 66 | 0 | self.send(str); |
| 67 | }; | |
| 68 | ||
| 69 | 0 | this.super_.render.call(this, view, options, fn); |
| 70 | } | |
| 71 | }; | |
| 72 | ||
| 73 | }; | |
| 74 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var fs = require('fs'), |
| 21 | path = require('path'), | |
| 22 | nconf = require('nconf'), | |
| 23 | shortstop = require('shortstop'); | |
| 24 | ||
| 25 | // Patches JSON | |
| 26 | 1 | require('jsonminify'); |
| 27 | ||
| 28 | ||
| 29 | ||
| 30 | 1 | function Configurator(appRoot) { |
| 31 | // Setup default memory store with settings from command line and env. | |
| 32 | 0 | this._nconf = nconf; |
| 33 | 0 | this._resolver = shortstop.create(); |
| 34 | 0 | this._init(appRoot); |
| 35 | } | |
| 36 | ||
| 37 | ||
| 38 | 1 | Configurator.Protocol = { |
| 39 | path: function (value) { | |
| 40 | 0 | if (path.resolve(value) === value) { |
| 41 | // Absolute path already, so just return it. | |
| 42 | 0 | return value; |
| 43 | } | |
| 44 | 0 | value = value.split('/'); |
| 45 | 0 | value.unshift(nconf.get('appRoot')); |
| 46 | 0 | return path.resolve.apply(path, value); |
| 47 | }, | |
| 48 | file: function (value) { | |
| 49 | 0 | value = Configurator.Protocol.path(value); |
| 50 | 0 | return fs.readFileSync(value); |
| 51 | }, | |
| 52 | base64: function (value) { | |
| 53 | 0 | return new Buffer(value, 'base64'); |
| 54 | } | |
| 55 | ||
| 56 | }; | |
| 57 | ||
| 58 | ||
| 59 | 1 | Configurator.ENVIRONMENTS = { |
| 60 | development: /^dev/i, | |
| 61 | test : /^test/i, | |
| 62 | staging : /^stag/i, | |
| 63 | production : /^prod/i | |
| 64 | }; | |
| 65 | ||
| 66 | ||
| 67 | /** | |
| 68 | * Helper function to find out if the provided dir is the filesystem root. | |
| 69 | * @param dir the directory in question | |
| 70 | * @returns {boolean} true if we're at the filesystem root, false if not. | |
| 71 | */ | |
| 72 | 1 | Configurator.isSystemRoot = function (dir) { |
| 73 | 0 | return path.dirname(dir) === dir; |
| 74 | }; | |
| 75 | ||
| 76 | ||
| 77 | 1 | Configurator.prototype = { |
| 78 | ||
| 79 | /** | |
| 80 | * Read the provided files and put their contents into nconf, resolving possible | |
| 81 | * env-specific files/config. | |
| 82 | * @param root | |
| 83 | * @param files | |
| 84 | */ | |
| 85 | load: function load(root, files) { | |
| 86 | 0 | var file, store, env, ext; |
| 87 | ||
| 88 | 0 | env = nconf.get('env:env'); |
| 89 | 0 | ext = '.json'; |
| 90 | ||
| 91 | 0 | root = this._findConfigRoot(root); |
| 92 | 0 | if (!root) { |
| 93 | 0 | return this; |
| 94 | } | |
| 95 | ||
| 96 | // Include env-specific files... | |
| 97 | 0 | files.forEach(function (fileName) { |
| 98 | 0 | file = path.join(root, fileName + '-' + env + ext); |
| 99 | 0 | store = this._createJsonStore(file); |
| 100 | ||
| 101 | 0 | if (store) { |
| 102 | 0 | nconf.use(file, store); |
| 103 | } | |
| 104 | }, this); | |
| 105 | ||
| 106 | // Then, include base files... | |
| 107 | 0 | files.forEach(function (fileName) { |
| 108 | 0 | file = path.join(root, fileName + ext); |
| 109 | 0 | store = this._createJsonStore(file); |
| 110 | ||
| 111 | 0 | if (store) { |
| 112 | 0 | nconf.use(file, store); |
| 113 | } | |
| 114 | }, this); | |
| 115 | ||
| 116 | 0 | return this; |
| 117 | }, | |
| 118 | ||
| 119 | ||
| 120 | done: function () { | |
| 121 | 0 | return this._nconf; |
| 122 | }, | |
| 123 | ||
| 124 | ||
| 125 | /** | |
| 126 | * Adds convenience properties for determining the application's | |
| 127 | * current environment (dev, test, prod, etc), configures protocol, etc. | |
| 128 | */ | |
| 129 | _init: function init(appRoot) { | |
| 130 | 0 | var nconf, env; |
| 131 | ||
| 132 | // Configure environment convenience properties. | |
| 133 | 0 | nconf = this._nconf; |
| 134 | ||
| 135 | 0 | nconf.argv().env().use('memory'); |
| 136 | 0 | env = nconf.get('NODE_ENV') || 'development'; |
| 137 | 0 | nconf.set('NODE_ENV', env); |
| 138 | 0 | nconf.set('env:env', env); |
| 139 | 0 | nconf.set('env:' + env, true); |
| 140 | 0 | nconf.set('appRoot', appRoot); |
| 141 | ||
| 142 | 0 | Object.keys(Configurator.ENVIRONMENTS).forEach(function (key) { |
| 143 | 0 | nconf.set('env:' + key, !!env.match(Configurator.ENVIRONMENTS[key])); |
| 144 | }); | |
| 145 | ||
| 146 | // Configure protocol handlers | |
| 147 | 0 | Object.keys(Configurator.Protocol).forEach(function (protocol) { |
| 148 | 0 | this._resolver.use(protocol, Configurator.Protocol[protocol]); |
| 149 | }, this); | |
| 150 | }, | |
| 151 | ||
| 152 | ||
| 153 | /** | |
| 154 | * Find the first config directory starting at the provided root. | |
| 155 | * @param dir the directory from which to start searching for the config dir. | |
| 156 | * @returns {undefined} the config directory or undefined if not found. | |
| 157 | */ | |
| 158 | _findConfigRoot: function (dir) { | |
| 159 | 0 | var pkg, root, exists; |
| 160 | ||
| 161 | 0 | do { |
| 162 | ||
| 163 | 0 | pkg = path.join(dir, 'config'); |
| 164 | 0 | dir = path.dirname(dir); |
| 165 | 0 | root = Configurator.isSystemRoot(dir); |
| 166 | ||
| 167 | } while (!(exists = fs.existsSync(pkg)) && !root); | |
| 168 | ||
| 169 | 0 | return exists ? pkg : undefined; |
| 170 | }, | |
| 171 | ||
| 172 | ||
| 173 | /** | |
| 174 | * Create a JSON nconf store for the given file. | |
| 175 | * @param file the file for which to create the JSON literal store. | |
| 176 | * @returns {{type: string, store: *}} | |
| 177 | */ | |
| 178 | _createJsonStore: function createJsonStore(file) { | |
| 179 | 0 | var contents; |
| 180 | ||
| 181 | 0 | if (fs.existsSync(file)) { |
| 182 | // Minify the code to remove the comments | |
| 183 | // NOTE: using fs.readFile instead of `require` | |
| 184 | // so JSON parsing doesn't happen automatically. | |
| 185 | 0 | contents = fs.readFileSync(file, 'utf8'); |
| 186 | 0 | contents = JSON.minify(contents); |
| 187 | 0 | if (typeof contents === 'string' && contents.length > 0) { |
| 188 | 0 | try { |
| 189 | 0 | contents = JSON.parse(contents); |
| 190 | 0 | contents = this._resolver.resolve(contents); |
| 191 | } catch (e) { | |
| 192 | 0 | throw new Error(e + " at " + file); |
| 193 | } | |
| 194 | 0 | return { |
| 195 | type: 'literal', | |
| 196 | store: contents | |
| 197 | }; | |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | 0 | return undefined; |
| 202 | } | |
| 203 | ||
| 204 | }; | |
| 205 | ||
| 206 | ||
| 207 | ||
| 208 | 1 | function getPort(nconf) { |
| 209 | 0 | var value, port, ports; |
| 210 | ||
| 211 | 0 | value = undefined; |
| 212 | 0 | port = undefined; |
| 213 | 0 | ports = nconf.get('port'); |
| 214 | 0 | ports = Array.isArray(ports) ? ports : [ ports ]; |
| 215 | 0 | ports.some(function (env) { |
| 216 | 0 | value = (typeof env === 'number') ? env : nconf.get(env); |
| 217 | 0 | port = parseInt(value, 10); |
| 218 | 0 | port = isNaN(port) ? value : port; |
| 219 | 0 | return !!port; |
| 220 | }); | |
| 221 | ||
| 222 | 0 | return port; |
| 223 | } | |
| 224 | ||
| 225 | ||
| 226 | 1 | function getHost(nconf) { |
| 227 | 0 | var host, hosts; |
| 228 | ||
| 229 | 0 | if (typeof getPort(nconf) === 'string') { |
| 230 | 0 | return undefined; |
| 231 | } | |
| 232 | ||
| 233 | 0 | host = undefined; |
| 234 | 0 | hosts = nconf.get('host'); |
| 235 | 0 | hosts = Array.isArray(hosts) ? hosts : [ hosts ]; |
| 236 | 0 | hosts.some(function (env) { |
| 237 | 0 | host = nconf.get(env); |
| 238 | 0 | return !!host; |
| 239 | }); | |
| 240 | ||
| 241 | 0 | return host; |
| 242 | } | |
| 243 | ||
| 244 | ||
| 245 | ||
| 246 | 1 | exports.create = function (appRoot) { |
| 247 | ||
| 248 | 0 | nconf = new Configurator(appRoot) |
| 249 | .load(appRoot, ['app', 'middleware']) | |
| 250 | .load(__dirname, ['webcore', 'middleware']) | |
| 251 | .done(); | |
| 252 | ||
| 253 | 0 | return Object.create(nconf, { |
| 254 | raw: { | |
| 255 | get: function () { | |
| 256 | 0 | return Object.getPrototypeOf(this); |
| 257 | } | |
| 258 | }, | |
| 259 | ||
| 260 | port: { | |
| 261 | enumerable: true, | |
| 262 | get: function () { | |
| 263 | 0 | return getPort(this); |
| 264 | } | |
| 265 | }, | |
| 266 | ||
| 267 | host: { | |
| 268 | enumerable: true, | |
| 269 | get: function () { | |
| 270 | 0 | return getHost(this); |
| 271 | } | |
| 272 | } | |
| 273 | }); | |
| 274 | }; | |
| 275 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | ||
| 21 | 1 | exports.tryRequire = function tryRequire(moduleName, fallback) { |
| 22 | 0 | var result; |
| 23 | 0 | try { |
| 24 | 0 | result = moduleName && require(moduleName); |
| 25 | } catch (err) { | |
| 26 | // noop | |
| 27 | } | |
| 28 | 0 | return result || fallback; |
| 29 | }; | |
| 30 | ||
| 31 | ||
| 32 | 1 | exports.configutil = require('./configutil'); |
| 33 | ||
| 34 | ||
| 35 | 1 | exports.pathutil = require('./pathutil'); |
| 36 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 2 | │ Copyright (C) 2014 eBay Software Foundation │ | |
| 3 | │ │ | |
| 4 | │hh ,'""`. │ | |
| 5 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 6 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 7 | │ ) __ ( You may obtain a copy of the License at │ | |
| 8 | │ /,'))((`.\ │ | |
| 9 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 10 | │ `\ `)(' /' │ | |
| 11 | │ │ | |
| 12 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 13 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 14 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 15 | │ See the License for the specific language governing permissions and │ | |
| 16 | │ limitations under the License. │ | |
| 17 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 18 | 1 | 'use strict'; |
| 19 | ||
| 20 | 1 | var path = require('path'); |
| 21 | ||
| 22 | ||
| 23 | 1 | var proto = { |
| 24 | ||
| 25 | resolve: function () { | |
| 26 | 0 | var args, segments, result; |
| 27 | ||
| 28 | 0 | args = Array.prototype.slice.call(arguments); |
| 29 | 0 | segments = args.reduce(function (prev, curr) { |
| 30 | 0 | if (curr !== undefined) { |
| 31 | 0 | if (!Array.isArray(curr)) { |
| 32 | 0 | curr = [curr]; |
| 33 | } | |
| 34 | 0 | return prev.concat(curr); |
| 35 | } | |
| 36 | 0 | return prev; |
| 37 | }, []); | |
| 38 | ||
| 39 | 0 | result = path.join.apply(null, segments); |
| 40 | 0 | if (path.resolve(result) === result) { |
| 41 | // already absolute path, so no need to use root | |
| 42 | 0 | return result; |
| 43 | } | |
| 44 | ||
| 45 | 0 | segments.unshift(this.root || (this.root = this._doResolve())); |
| 46 | 0 | return path.join.apply(null, segments); |
| 47 | } | |
| 48 | ||
| 49 | }; | |
| 50 | ||
| 51 | ||
| 52 | 1 | exports.create = function (resolver) { |
| 53 | 0 | return Object.create(proto, { |
| 54 | root: { | |
| 55 | value: undefined, | |
| 56 | enumerable: true, | |
| 57 | writable: true | |
| 58 | }, | |
| 59 | ||
| 60 | _doResolve: { | |
| 61 | 0 | value: resolver || function () { return process.cwd(); } |
| 62 | } | |
| 63 | }); | |
| 64 | }; | |
| 65 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var util = require('util'), |
| 4 | WriteStream = require('fs').WriteStream, | |
| 5 | EventEmitter = require('events').EventEmitter, | |
| 6 | crypto = require('crypto'); | |
| 7 | ||
| 8 | 1 | function File(properties) { |
| 9 | 0 | EventEmitter.call(this); |
| 10 | ||
| 11 | 0 | this.size = 0; |
| 12 | 0 | this.path = null; |
| 13 | 0 | this.name = null; |
| 14 | 0 | this.type = null; |
| 15 | 0 | this.hash = null; |
| 16 | 0 | this.lastModifiedDate = null; |
| 17 | ||
| 18 | 0 | this._writeStream = null; |
| 19 | ||
| 20 | 0 | for (var key in properties) { |
| 21 | 0 | this[key] = properties[key]; |
| 22 | } | |
| 23 | ||
| 24 | 0 | if(typeof this.hash === 'string') { |
| 25 | 0 | this.hash = crypto.createHash(properties.hash); |
| 26 | } else { | |
| 27 | 0 | this.hash = null; |
| 28 | } | |
| 29 | } | |
| 30 | 1 | module.exports = File; |
| 31 | 1 | util.inherits(File, EventEmitter); |
| 32 | ||
| 33 | 1 | File.prototype.open = function() { |
| 34 | 0 | this._writeStream = new WriteStream(this.path); |
| 35 | }; | |
| 36 | ||
| 37 | 1 | File.prototype.toJSON = function() { |
| 38 | 0 | return { |
| 39 | size: this.size, | |
| 40 | path: this.path, | |
| 41 | name: this.name, | |
| 42 | type: this.type, | |
| 43 | mtime: this.lastModifiedDate, | |
| 44 | length: this.length, | |
| 45 | filename: this.filename, | |
| 46 | mime: this.mime | |
| 47 | }; | |
| 48 | }; | |
| 49 | ||
| 50 | 1 | File.prototype.write = function(buffer, cb) { |
| 51 | 0 | var self = this; |
| 52 | 0 | if (self.hash) { |
| 53 | 0 | self.hash.update(buffer); |
| 54 | } | |
| 55 | 0 | this._writeStream.write(buffer, function() { |
| 56 | 0 | self.lastModifiedDate = new Date(); |
| 57 | 0 | self.size += buffer.length; |
| 58 | 0 | self.emit('progress', self.size); |
| 59 | 0 | cb(); |
| 60 | }); | |
| 61 | }; | |
| 62 | ||
| 63 | 1 | File.prototype.end = function(cb) { |
| 64 | 0 | var self = this; |
| 65 | 0 | if (self.hash) { |
| 66 | 0 | self.hash = self.hash.digest('hex'); |
| 67 | } | |
| 68 | 0 | this._writeStream.end(function() { |
| 69 | 0 | self.emit('end'); |
| 70 | 0 | cb(); |
| 71 | }); | |
| 72 | }; | |
| 73 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var fs = require('fs'); |
| 4 | 1 | var util = require('util'), |
| 5 | path = require('path'), | |
| 6 | File = require('./file'), | |
| 7 | MultipartParser = require('./multipart_parser').MultipartParser, | |
| 8 | QuerystringParser = require('./querystring_parser').QuerystringParser, | |
| 9 | OctetParser = require('./octet_parser').OctetParser, | |
| 10 | JSONParser = require('./json_parser').JSONParser, | |
| 11 | StringDecoder = require('string_decoder').StringDecoder, | |
| 12 | EventEmitter = require('events').EventEmitter, | |
| 13 | Stream = require('stream').Stream, | |
| 14 | os = require('os'); | |
| 15 | ||
| 16 | 1 | function IncomingForm(opts) { |
| 17 | 0 | if (!(this instanceof IncomingForm)) return new IncomingForm(opts); |
| 18 | 0 | EventEmitter.call(this); |
| 19 | ||
| 20 | 0 | opts=opts||{}; |
| 21 | ||
| 22 | 0 | this.error = null; |
| 23 | 0 | this.ended = false; |
| 24 | ||
| 25 | 0 | this.maxFields = opts.maxFields || 1000; |
| 26 | 0 | this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024; |
| 27 | 0 | this.keepExtensions = opts.keepExtensions || false; |
| 28 | 0 | this.uploadDir = opts.uploadDir || os.tmpDir(); |
| 29 | 0 | this.encoding = opts.encoding || 'utf-8'; |
| 30 | 0 | this.headers = null; |
| 31 | 0 | this.type = null; |
| 32 | 0 | this.hash = false; |
| 33 | ||
| 34 | 0 | this.bytesReceived = null; |
| 35 | 0 | this.bytesExpected = null; |
| 36 | ||
| 37 | 0 | this._parser = null; |
| 38 | 0 | this._flushing = 0; |
| 39 | 0 | this._fieldsSize = 0; |
| 40 | 0 | this.openedFiles = []; |
| 41 | ||
| 42 | 0 | return this; |
| 43 | }; | |
| 44 | 1 | util.inherits(IncomingForm, EventEmitter); |
| 45 | 1 | exports.IncomingForm = IncomingForm; |
| 46 | ||
| 47 | 1 | IncomingForm.prototype.parse = function(req, cb) { |
| 48 | 0 | this.pause = function() { |
| 49 | 0 | try { |
| 50 | 0 | req.pause(); |
| 51 | } catch (err) { | |
| 52 | // the stream was destroyed | |
| 53 | 0 | if (!this.ended) { |
| 54 | // before it was completed, crash & burn | |
| 55 | 0 | this._error(err); |
| 56 | } | |
| 57 | 0 | return false; |
| 58 | } | |
| 59 | 0 | return true; |
| 60 | }; | |
| 61 | ||
| 62 | 0 | this.resume = function() { |
| 63 | 0 | try { |
| 64 | 0 | req.resume(); |
| 65 | } catch (err) { | |
| 66 | // the stream was destroyed | |
| 67 | 0 | if (!this.ended) { |
| 68 | // before it was completed, crash & burn | |
| 69 | 0 | this._error(err); |
| 70 | } | |
| 71 | 0 | return false; |
| 72 | } | |
| 73 | ||
| 74 | 0 | return true; |
| 75 | }; | |
| 76 | ||
| 77 | // Setup callback first, so we don't miss anything from data events emitted | |
| 78 | // immediately. | |
| 79 | 0 | if (cb) { |
| 80 | 0 | var fields = {}, files = {}; |
| 81 | 0 | this |
| 82 | .on('field', function(name, value) { | |
| 83 | 0 | fields[name] = value; |
| 84 | }) | |
| 85 | .on('file', function(name, file) { | |
| 86 | 0 | files[name] = file; |
| 87 | }) | |
| 88 | .on('error', function(err) { | |
| 89 | 0 | cb(err, fields, files); |
| 90 | }) | |
| 91 | .on('end', function() { | |
| 92 | 0 | cb(null, fields, files); |
| 93 | }); | |
| 94 | } | |
| 95 | ||
| 96 | // Parse headers and setup the parser, ready to start listening for data. | |
| 97 | 0 | this.writeHeaders(req.headers); |
| 98 | ||
| 99 | // Start listening for data. | |
| 100 | 0 | var self = this; |
| 101 | 0 | req |
| 102 | .on('error', function(err) { | |
| 103 | 0 | self._error(err); |
| 104 | }) | |
| 105 | .on('aborted', function() { | |
| 106 | 0 | self.emit('aborted'); |
| 107 | 0 | self._error(new Error('Request aborted')); |
| 108 | }) | |
| 109 | .on('data', function(buffer) { | |
| 110 | 0 | self.write(buffer); |
| 111 | }) | |
| 112 | .on('end', function() { | |
| 113 | 0 | if (self.error) { |
| 114 | 0 | return; |
| 115 | } | |
| 116 | ||
| 117 | 0 | var err = self._parser.end(); |
| 118 | 0 | if (err) { |
| 119 | 0 | self._error(err); |
| 120 | } | |
| 121 | }); | |
| 122 | ||
| 123 | 0 | return this; |
| 124 | }; | |
| 125 | ||
| 126 | 1 | IncomingForm.prototype.writeHeaders = function(headers) { |
| 127 | 0 | this.headers = headers; |
| 128 | 0 | this._parseContentLength(); |
| 129 | 0 | this._parseContentType(); |
| 130 | }; | |
| 131 | ||
| 132 | 1 | IncomingForm.prototype.write = function(buffer) { |
| 133 | 0 | if (!this._parser) { |
| 134 | 0 | this._error(new Error('unintialized parser')); |
| 135 | 0 | return; |
| 136 | } | |
| 137 | ||
| 138 | 0 | this.bytesReceived += buffer.length; |
| 139 | 0 | this.emit('progress', this.bytesReceived, this.bytesExpected); |
| 140 | ||
| 141 | 0 | var bytesParsed = this._parser.write(buffer); |
| 142 | 0 | if (bytesParsed !== buffer.length) { |
| 143 | 0 | this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed')); |
| 144 | } | |
| 145 | ||
| 146 | 0 | return bytesParsed; |
| 147 | }; | |
| 148 | ||
| 149 | 1 | IncomingForm.prototype.pause = function() { |
| 150 | // this does nothing, unless overwritten in IncomingForm.parse | |
| 151 | 0 | return false; |
| 152 | }; | |
| 153 | ||
| 154 | 1 | IncomingForm.prototype.resume = function() { |
| 155 | // this does nothing, unless overwritten in IncomingForm.parse | |
| 156 | 0 | return false; |
| 157 | }; | |
| 158 | ||
| 159 | 1 | IncomingForm.prototype.onPart = function(part) { |
| 160 | // this method can be overwritten by the user | |
| 161 | 0 | this.handlePart(part); |
| 162 | }; | |
| 163 | ||
| 164 | 1 | IncomingForm.prototype.handlePart = function(part) { |
| 165 | 0 | var self = this; |
| 166 | ||
| 167 | 0 | if (part.filename === undefined) { |
| 168 | 0 | var value = '' |
| 169 | , decoder = new StringDecoder(this.encoding); | |
| 170 | ||
| 171 | 0 | part.on('data', function(buffer) { |
| 172 | 0 | self._fieldsSize += buffer.length; |
| 173 | 0 | if (self._fieldsSize > self.maxFieldsSize) { |
| 174 | 0 | self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data')); |
| 175 | 0 | return; |
| 176 | } | |
| 177 | 0 | value += decoder.write(buffer); |
| 178 | }); | |
| 179 | ||
| 180 | 0 | part.on('end', function() { |
| 181 | 0 | self.emit('field', part.name, value); |
| 182 | }); | |
| 183 | 0 | return; |
| 184 | } | |
| 185 | ||
| 186 | 0 | this._flushing++; |
| 187 | ||
| 188 | 0 | var file = new File({ |
| 189 | path: this._uploadPath(part.filename), | |
| 190 | name: part.filename, | |
| 191 | type: part.mime, | |
| 192 | hash: self.hash | |
| 193 | }); | |
| 194 | ||
| 195 | 0 | this.emit('fileBegin', part.name, file); |
| 196 | ||
| 197 | 0 | file.open(); |
| 198 | 0 | this.openedFiles.push(file); |
| 199 | ||
| 200 | 0 | part.on('data', function(buffer) { |
| 201 | 0 | self.pause(); |
| 202 | 0 | file.write(buffer, function() { |
| 203 | 0 | self.resume(); |
| 204 | }); | |
| 205 | }); | |
| 206 | ||
| 207 | 0 | part.on('end', function() { |
| 208 | 0 | file.end(function() { |
| 209 | 0 | self._flushing--; |
| 210 | 0 | self.emit('file', part.name, file); |
| 211 | 0 | self._maybeEnd(); |
| 212 | }); | |
| 213 | }); | |
| 214 | }; | |
| 215 | ||
| 216 | 1 | function dummyParser(self) { |
| 217 | 0 | return { |
| 218 | end: function () { | |
| 219 | 0 | self.ended = true; |
| 220 | 0 | self._maybeEnd(); |
| 221 | 0 | return null; |
| 222 | } | |
| 223 | }; | |
| 224 | } | |
| 225 | ||
| 226 | 1 | IncomingForm.prototype._parseContentType = function() { |
| 227 | 0 | if (this.bytesExpected === 0) { |
| 228 | 0 | this._parser = dummyParser(this); |
| 229 | 0 | return; |
| 230 | } | |
| 231 | ||
| 232 | 0 | if (!this.headers['content-type']) { |
| 233 | 0 | this._error(new Error('bad content-type header, no content-type')); |
| 234 | 0 | return; |
| 235 | } | |
| 236 | ||
| 237 | 0 | if (this.headers['content-type'].match(/octet-stream/i)) { |
| 238 | 0 | this._initOctetStream(); |
| 239 | 0 | return; |
| 240 | } | |
| 241 | ||
| 242 | 0 | if (this.headers['content-type'].match(/urlencoded/i)) { |
| 243 | 0 | this._initUrlencoded(); |
| 244 | 0 | return; |
| 245 | } | |
| 246 | ||
| 247 | 0 | if (this.headers['content-type'].match(/multipart/i)) { |
| 248 | 0 | var m; |
| 249 | 0 | if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) { |
| 250 | 0 | this._initMultipart(m[1] || m[2]); |
| 251 | } else { | |
| 252 | 0 | this._error(new Error('bad content-type header, no multipart boundary')); |
| 253 | } | |
| 254 | 0 | return; |
| 255 | } | |
| 256 | ||
| 257 | 0 | if (this.headers['content-type'].match(/json/i)) { |
| 258 | 0 | this._initJSONencoded(); |
| 259 | 0 | return; |
| 260 | } | |
| 261 | ||
| 262 | 0 | this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type'])); |
| 263 | }; | |
| 264 | ||
| 265 | 1 | IncomingForm.prototype._error = function(err) { |
| 266 | 0 | if (this.error || this.ended) { |
| 267 | 0 | return; |
| 268 | } | |
| 269 | ||
| 270 | 0 | this.error = err; |
| 271 | 0 | this.pause(); |
| 272 | 0 | this.emit('error', err); |
| 273 | ||
| 274 | 0 | if (Array.isArray(this.openedFiles)) { |
| 275 | 0 | this.openedFiles.forEach(function(file) { |
| 276 | 0 | file._writeStream.destroy(); |
| 277 | 0 | setTimeout(fs.unlink, 0, file.path); |
| 278 | }); | |
| 279 | } | |
| 280 | }; | |
| 281 | ||
| 282 | 1 | IncomingForm.prototype._parseContentLength = function() { |
| 283 | 0 | this.bytesReceived = 0; |
| 284 | 0 | if (this.headers['content-length']) { |
| 285 | 0 | this.bytesExpected = parseInt(this.headers['content-length'], 10); |
| 286 | 0 | } else if (this.headers['transfer-encoding'] === undefined) { |
| 287 | 0 | this.bytesExpected = 0; |
| 288 | } | |
| 289 | ||
| 290 | 0 | if (this.bytesExpected !== null) { |
| 291 | 0 | this.emit('progress', this.bytesReceived, this.bytesExpected); |
| 292 | } | |
| 293 | }; | |
| 294 | ||
| 295 | 1 | IncomingForm.prototype._newParser = function() { |
| 296 | 0 | return new MultipartParser(); |
| 297 | }; | |
| 298 | ||
| 299 | 1 | IncomingForm.prototype._initMultipart = function(boundary) { |
| 300 | 0 | this.type = 'multipart'; |
| 301 | ||
| 302 | 0 | var parser = new MultipartParser(), |
| 303 | self = this, | |
| 304 | headerField, | |
| 305 | headerValue, | |
| 306 | part; | |
| 307 | ||
| 308 | 0 | parser.initWithBoundary(boundary); |
| 309 | ||
| 310 | 0 | parser.onPartBegin = function() { |
| 311 | 0 | part = new Stream(); |
| 312 | 0 | part.readable = true; |
| 313 | 0 | part.headers = {}; |
| 314 | 0 | part.name = null; |
| 315 | 0 | part.filename = null; |
| 316 | 0 | part.mime = null; |
| 317 | ||
| 318 | 0 | part.transferEncoding = 'binary'; |
| 319 | 0 | part.transferBuffer = ''; |
| 320 | ||
| 321 | 0 | headerField = ''; |
| 322 | 0 | headerValue = ''; |
| 323 | }; | |
| 324 | ||
| 325 | 0 | parser.onHeaderField = function(b, start, end) { |
| 326 | 0 | headerField += b.toString(self.encoding, start, end); |
| 327 | }; | |
| 328 | ||
| 329 | 0 | parser.onHeaderValue = function(b, start, end) { |
| 330 | 0 | headerValue += b.toString(self.encoding, start, end); |
| 331 | }; | |
| 332 | ||
| 333 | 0 | parser.onHeaderEnd = function() { |
| 334 | 0 | headerField = headerField.toLowerCase(); |
| 335 | 0 | part.headers[headerField] = headerValue; |
| 336 | ||
| 337 | 0 | var m; |
| 338 | 0 | if (headerField == 'content-disposition') { |
| 339 | 0 | if (m = headerValue.match(/\bname="([^"]+)"/i)) { |
| 340 | 0 | part.name = m[1]; |
| 341 | } | |
| 342 | ||
| 343 | 0 | part.filename = self._fileName(headerValue); |
| 344 | 0 | } else if (headerField == 'content-type') { |
| 345 | 0 | part.mime = headerValue; |
| 346 | 0 | } else if (headerField == 'content-transfer-encoding') { |
| 347 | 0 | part.transferEncoding = headerValue.toLowerCase(); |
| 348 | } | |
| 349 | ||
| 350 | 0 | headerField = ''; |
| 351 | 0 | headerValue = ''; |
| 352 | }; | |
| 353 | ||
| 354 | 0 | parser.onHeadersEnd = function() { |
| 355 | 0 | switch(part.transferEncoding){ |
| 356 | case 'binary': | |
| 357 | case '7bit': | |
| 358 | case '8bit': | |
| 359 | 0 | parser.onPartData = function(b, start, end) { |
| 360 | 0 | part.emit('data', b.slice(start, end)); |
| 361 | }; | |
| 362 | ||
| 363 | 0 | parser.onPartEnd = function() { |
| 364 | 0 | part.emit('end'); |
| 365 | }; | |
| 366 | 0 | break; |
| 367 | ||
| 368 | case 'base64': | |
| 369 | 0 | parser.onPartData = function(b, start, end) { |
| 370 | 0 | part.transferBuffer += b.slice(start, end).toString('ascii'); |
| 371 | ||
| 372 | /* | |
| 373 | four bytes (chars) in base64 converts to three bytes in binary | |
| 374 | encoding. So we should always work with a number of bytes that | |
| 375 | can be divided by 4, it will result in a number of buytes that | |
| 376 | can be divided vy 3. | |
| 377 | */ | |
| 378 | 0 | var offset = parseInt(part.transferBuffer.length / 4) * 4; |
| 379 | 0 | part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64')) |
| 380 | 0 | part.transferBuffer = part.transferBuffer.substring(offset); |
| 381 | }; | |
| 382 | ||
| 383 | 0 | parser.onPartEnd = function() { |
| 384 | 0 | part.emit('data', new Buffer(part.transferBuffer, 'base64')) |
| 385 | 0 | part.emit('end'); |
| 386 | }; | |
| 387 | 0 | break; |
| 388 | ||
| 389 | default: | |
| 390 | 0 | return self._error(new Error('unknown transfer-encoding')); |
| 391 | } | |
| 392 | ||
| 393 | 0 | self.onPart(part); |
| 394 | }; | |
| 395 | ||
| 396 | ||
| 397 | 0 | parser.onEnd = function() { |
| 398 | 0 | self.ended = true; |
| 399 | 0 | self._maybeEnd(); |
| 400 | }; | |
| 401 | ||
| 402 | 0 | this._parser = parser; |
| 403 | }; | |
| 404 | ||
| 405 | 1 | IncomingForm.prototype._fileName = function(headerValue) { |
| 406 | 0 | var m = headerValue.match(/\bfilename="(.*?)"($|; )/i); |
| 407 | 0 | if (!m) return; |
| 408 | ||
| 409 | 0 | var filename = m[1].substr(m[1].lastIndexOf('\\') + 1); |
| 410 | 0 | filename = filename.replace(/%22/g, '"'); |
| 411 | 0 | filename = filename.replace(/&#([\d]{4});/g, function(m, code) { |
| 412 | 0 | return String.fromCharCode(code); |
| 413 | }); | |
| 414 | 0 | return filename; |
| 415 | }; | |
| 416 | ||
| 417 | 1 | IncomingForm.prototype._initUrlencoded = function() { |
| 418 | 0 | this.type = 'urlencoded'; |
| 419 | ||
| 420 | 0 | var parser = new QuerystringParser(this.maxFields) |
| 421 | , self = this; | |
| 422 | ||
| 423 | 0 | parser.onField = function(key, val) { |
| 424 | 0 | self.emit('field', key, val); |
| 425 | }; | |
| 426 | ||
| 427 | 0 | parser.onEnd = function() { |
| 428 | 0 | self.ended = true; |
| 429 | 0 | self._maybeEnd(); |
| 430 | }; | |
| 431 | ||
| 432 | 0 | this._parser = parser; |
| 433 | }; | |
| 434 | ||
| 435 | 1 | IncomingForm.prototype._initOctetStream = function() { |
| 436 | 0 | this.type = 'octet-stream'; |
| 437 | 0 | var filename = this.headers['x-file-name']; |
| 438 | 0 | var mime = this.headers['content-type']; |
| 439 | ||
| 440 | 0 | var file = new File({ |
| 441 | path: this._uploadPath(filename), | |
| 442 | name: filename, | |
| 443 | type: mime | |
| 444 | }); | |
| 445 | ||
| 446 | 0 | file.open(); |
| 447 | ||
| 448 | 0 | this.emit('fileBegin', filename, file); |
| 449 | ||
| 450 | 0 | this._flushing++; |
| 451 | ||
| 452 | 0 | var self = this; |
| 453 | ||
| 454 | 0 | self._parser = new OctetParser(); |
| 455 | ||
| 456 | //Keep track of writes that haven't finished so we don't emit the file before it's done being written | |
| 457 | 0 | var outstandingWrites = 0; |
| 458 | ||
| 459 | 0 | self._parser.on('data', function(buffer){ |
| 460 | 0 | self.pause(); |
| 461 | 0 | outstandingWrites++; |
| 462 | ||
| 463 | 0 | file.write(buffer, function() { |
| 464 | 0 | outstandingWrites--; |
| 465 | 0 | self.resume(); |
| 466 | ||
| 467 | 0 | if(self.ended){ |
| 468 | 0 | self._parser.emit('doneWritingFile'); |
| 469 | } | |
| 470 | }); | |
| 471 | }); | |
| 472 | ||
| 473 | 0 | self._parser.on('end', function(){ |
| 474 | 0 | self._flushing--; |
| 475 | 0 | self.ended = true; |
| 476 | ||
| 477 | 0 | var done = function(){ |
| 478 | 0 | self.emit('file', 'file', file); |
| 479 | 0 | self._maybeEnd(); |
| 480 | }; | |
| 481 | ||
| 482 | 0 | if(outstandingWrites === 0){ |
| 483 | 0 | done(); |
| 484 | } else { | |
| 485 | 0 | self._parser.once('doneWritingFile', done); |
| 486 | } | |
| 487 | }); | |
| 488 | }; | |
| 489 | ||
| 490 | 1 | IncomingForm.prototype._initJSONencoded = function() { |
| 491 | 0 | this.type = 'json'; |
| 492 | ||
| 493 | 0 | var parser = new JSONParser() |
| 494 | , self = this; | |
| 495 | ||
| 496 | 0 | if (this.bytesExpected) { |
| 497 | 0 | parser.initWithLength(this.bytesExpected); |
| 498 | } | |
| 499 | ||
| 500 | 0 | parser.onField = function(key, val) { |
| 501 | 0 | self.emit('field', key, val); |
| 502 | } | |
| 503 | ||
| 504 | 0 | parser.onEnd = function() { |
| 505 | 0 | self.ended = true; |
| 506 | 0 | self._maybeEnd(); |
| 507 | }; | |
| 508 | ||
| 509 | 0 | this._parser = parser; |
| 510 | }; | |
| 511 | ||
| 512 | 1 | IncomingForm.prototype._uploadPath = function(filename) { |
| 513 | 0 | var name = ''; |
| 514 | 0 | for (var i = 0; i < 32; i++) { |
| 515 | 0 | name += Math.floor(Math.random() * 16).toString(16); |
| 516 | } | |
| 517 | ||
| 518 | 0 | if (this.keepExtensions) { |
| 519 | 0 | var ext = path.extname(filename); |
| 520 | 0 | ext = ext.replace(/(\.[a-z0-9]+).*/, '$1'); |
| 521 | ||
| 522 | 0 | name += ext; |
| 523 | } | |
| 524 | ||
| 525 | 0 | return path.join(this.uploadDir, name); |
| 526 | }; | |
| 527 | ||
| 528 | 1 | IncomingForm.prototype._maybeEnd = function() { |
| 529 | 0 | if (!this.ended || this._flushing || this.error) { |
| 530 | 0 | return; |
| 531 | } | |
| 532 | ||
| 533 | 0 | this.emit('end'); |
| 534 | }; | |
| 535 | ||
| 536 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var IncomingForm = require('./incoming_form').IncomingForm; |
| 2 | 1 | IncomingForm.IncomingForm = IncomingForm; |
| 3 | 1 | module.exports = IncomingForm; |
| 4 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var Buffer = require('buffer').Buffer |
| 4 | ||
| 5 | 1 | function JSONParser() { |
| 6 | 0 | this.data = new Buffer(''); |
| 7 | 0 | this.bytesWritten = 0; |
| 8 | }; | |
| 9 | 1 | exports.JSONParser = JSONParser; |
| 10 | ||
| 11 | 1 | JSONParser.prototype.initWithLength = function(length) { |
| 12 | 0 | this.data = new Buffer(length); |
| 13 | } | |
| 14 | ||
| 15 | 1 | JSONParser.prototype.write = function(buffer) { |
| 16 | 0 | if (this.data.length >= this.bytesWritten + buffer.length) { |
| 17 | 0 | buffer.copy(this.data, this.bytesWritten); |
| 18 | } else { | |
| 19 | 0 | this.data = Buffer.concat([this.data, buffer]); |
| 20 | } | |
| 21 | 0 | this.bytesWritten += buffer.length; |
| 22 | 0 | return buffer.length; |
| 23 | } | |
| 24 | ||
| 25 | 1 | JSONParser.prototype.end = function() { |
| 26 | 0 | try { |
| 27 | 0 | var fields = JSON.parse(this.data.toString('utf8')) |
| 28 | 0 | for (var field in fields) { |
| 29 | 0 | this.onField(field, fields[field]); |
| 30 | } | |
| 31 | } catch (e) {} | |
| 32 | 0 | this.data = null; |
| 33 | ||
| 34 | 0 | this.onEnd(); |
| 35 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Buffer = require('buffer').Buffer, |
| 2 | s = 0, | |
| 3 | S = | |
| 4 | { PARSER_UNINITIALIZED: s++, | |
| 5 | START: s++, | |
| 6 | START_BOUNDARY: s++, | |
| 7 | HEADER_FIELD_START: s++, | |
| 8 | HEADER_FIELD: s++, | |
| 9 | HEADER_VALUE_START: s++, | |
| 10 | HEADER_VALUE: s++, | |
| 11 | HEADER_VALUE_ALMOST_DONE: s++, | |
| 12 | HEADERS_ALMOST_DONE: s++, | |
| 13 | PART_DATA_START: s++, | |
| 14 | PART_DATA: s++, | |
| 15 | PART_END: s++, | |
| 16 | END: s++ | |
| 17 | }, | |
| 18 | ||
| 19 | f = 1, | |
| 20 | F = | |
| 21 | { PART_BOUNDARY: f, | |
| 22 | LAST_BOUNDARY: f *= 2 | |
| 23 | }, | |
| 24 | ||
| 25 | LF = 10, | |
| 26 | CR = 13, | |
| 27 | SPACE = 32, | |
| 28 | HYPHEN = 45, | |
| 29 | COLON = 58, | |
| 30 | A = 97, | |
| 31 | Z = 122, | |
| 32 | ||
| 33 | lower = function(c) { | |
| 34 | 0 | return c | 0x20; |
| 35 | }; | |
| 36 | ||
| 37 | 1 | for (s in S) { |
| 38 | 13 | exports[s] = S[s]; |
| 39 | } | |
| 40 | ||
| 41 | 1 | function MultipartParser() { |
| 42 | 0 | this.boundary = null; |
| 43 | 0 | this.boundaryChars = null; |
| 44 | 0 | this.lookbehind = null; |
| 45 | 0 | this.state = S.PARSER_UNINITIALIZED; |
| 46 | ||
| 47 | 0 | this.index = null; |
| 48 | 0 | this.flags = 0; |
| 49 | }; | |
| 50 | 1 | exports.MultipartParser = MultipartParser; |
| 51 | ||
| 52 | 1 | MultipartParser.stateToString = function(stateNumber) { |
| 53 | 0 | for (var state in S) { |
| 54 | 0 | var number = S[state]; |
| 55 | 0 | if (number === stateNumber) return state; |
| 56 | } | |
| 57 | }; | |
| 58 | ||
| 59 | 1 | MultipartParser.prototype.initWithBoundary = function(str) { |
| 60 | 0 | this.boundary = new Buffer(str.length+4); |
| 61 | 0 | this.boundary.write('\r\n--', 'ascii', 0); |
| 62 | 0 | this.boundary.write(str, 'ascii', 4); |
| 63 | 0 | this.lookbehind = new Buffer(this.boundary.length+8); |
| 64 | 0 | this.state = S.START; |
| 65 | ||
| 66 | 0 | this.boundaryChars = {}; |
| 67 | 0 | for (var i = 0; i < this.boundary.length; i++) { |
| 68 | 0 | this.boundaryChars[this.boundary[i]] = true; |
| 69 | } | |
| 70 | }; | |
| 71 | ||
| 72 | 1 | MultipartParser.prototype.write = function(buffer) { |
| 73 | 0 | var self = this, |
| 74 | i = 0, | |
| 75 | len = buffer.length, | |
| 76 | prevIndex = this.index, | |
| 77 | index = this.index, | |
| 78 | state = this.state, | |
| 79 | flags = this.flags, | |
| 80 | lookbehind = this.lookbehind, | |
| 81 | boundary = this.boundary, | |
| 82 | boundaryChars = this.boundaryChars, | |
| 83 | boundaryLength = this.boundary.length, | |
| 84 | boundaryEnd = boundaryLength - 1, | |
| 85 | bufferLength = buffer.length, | |
| 86 | c, | |
| 87 | cl, | |
| 88 | ||
| 89 | mark = function(name) { | |
| 90 | 0 | self[name+'Mark'] = i; |
| 91 | }, | |
| 92 | clear = function(name) { | |
| 93 | 0 | delete self[name+'Mark']; |
| 94 | }, | |
| 95 | callback = function(name, buffer, start, end) { | |
| 96 | 0 | if (start !== undefined && start === end) { |
| 97 | 0 | return; |
| 98 | } | |
| 99 | ||
| 100 | 0 | var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); |
| 101 | 0 | if (callbackSymbol in self) { |
| 102 | 0 | self[callbackSymbol](buffer, start, end); |
| 103 | } | |
| 104 | }, | |
| 105 | dataCallback = function(name, clear) { | |
| 106 | 0 | var markSymbol = name+'Mark'; |
| 107 | 0 | if (!(markSymbol in self)) { |
| 108 | 0 | return; |
| 109 | } | |
| 110 | ||
| 111 | 0 | if (!clear) { |
| 112 | 0 | callback(name, buffer, self[markSymbol], buffer.length); |
| 113 | 0 | self[markSymbol] = 0; |
| 114 | } else { | |
| 115 | 0 | callback(name, buffer, self[markSymbol], i); |
| 116 | 0 | delete self[markSymbol]; |
| 117 | } | |
| 118 | }; | |
| 119 | ||
| 120 | 0 | for (i = 0; i < len; i++) { |
| 121 | 0 | c = buffer[i]; |
| 122 | 0 | switch (state) { |
| 123 | case S.PARSER_UNINITIALIZED: | |
| 124 | 0 | return i; |
| 125 | case S.START: | |
| 126 | 0 | index = 0; |
| 127 | 0 | state = S.START_BOUNDARY; |
| 128 | case S.START_BOUNDARY: | |
| 129 | 0 | if (index == boundary.length - 2) { |
| 130 | 0 | if (c != CR) { |
| 131 | 0 | return i; |
| 132 | } | |
| 133 | 0 | index++; |
| 134 | 0 | break; |
| 135 | 0 | } else if (index - 1 == boundary.length - 2) { |
| 136 | 0 | if (c != LF) { |
| 137 | 0 | return i; |
| 138 | } | |
| 139 | 0 | index = 0; |
| 140 | 0 | callback('partBegin'); |
| 141 | 0 | state = S.HEADER_FIELD_START; |
| 142 | 0 | break; |
| 143 | } | |
| 144 | ||
| 145 | 0 | if (c != boundary[index+2]) { |
| 146 | 0 | index = -2; |
| 147 | } | |
| 148 | 0 | if (c == boundary[index+2]) { |
| 149 | 0 | index++; |
| 150 | } | |
| 151 | 0 | break; |
| 152 | case S.HEADER_FIELD_START: | |
| 153 | 0 | state = S.HEADER_FIELD; |
| 154 | 0 | mark('headerField'); |
| 155 | 0 | index = 0; |
| 156 | case S.HEADER_FIELD: | |
| 157 | 0 | if (c == CR) { |
| 158 | 0 | clear('headerField'); |
| 159 | 0 | state = S.HEADERS_ALMOST_DONE; |
| 160 | 0 | break; |
| 161 | } | |
| 162 | ||
| 163 | 0 | index++; |
| 164 | 0 | if (c == HYPHEN) { |
| 165 | 0 | break; |
| 166 | } | |
| 167 | ||
| 168 | 0 | if (c == COLON) { |
| 169 | 0 | if (index == 1) { |
| 170 | // empty header field | |
| 171 | 0 | return i; |
| 172 | } | |
| 173 | 0 | dataCallback('headerField', true); |
| 174 | 0 | state = S.HEADER_VALUE_START; |
| 175 | 0 | break; |
| 176 | } | |
| 177 | ||
| 178 | 0 | cl = lower(c); |
| 179 | 0 | if (cl < A || cl > Z) { |
| 180 | 0 | return i; |
| 181 | } | |
| 182 | 0 | break; |
| 183 | case S.HEADER_VALUE_START: | |
| 184 | 0 | if (c == SPACE) { |
| 185 | 0 | break; |
| 186 | } | |
| 187 | ||
| 188 | 0 | mark('headerValue'); |
| 189 | 0 | state = S.HEADER_VALUE; |
| 190 | case S.HEADER_VALUE: | |
| 191 | 0 | if (c == CR) { |
| 192 | 0 | dataCallback('headerValue', true); |
| 193 | 0 | callback('headerEnd'); |
| 194 | 0 | state = S.HEADER_VALUE_ALMOST_DONE; |
| 195 | } | |
| 196 | 0 | break; |
| 197 | case S.HEADER_VALUE_ALMOST_DONE: | |
| 198 | 0 | if (c != LF) { |
| 199 | 0 | return i; |
| 200 | } | |
| 201 | 0 | state = S.HEADER_FIELD_START; |
| 202 | 0 | break; |
| 203 | case S.HEADERS_ALMOST_DONE: | |
| 204 | 0 | if (c != LF) { |
| 205 | 0 | return i; |
| 206 | } | |
| 207 | ||
| 208 | 0 | callback('headersEnd'); |
| 209 | 0 | state = S.PART_DATA_START; |
| 210 | 0 | break; |
| 211 | case S.PART_DATA_START: | |
| 212 | 0 | state = S.PART_DATA; |
| 213 | 0 | mark('partData'); |
| 214 | case S.PART_DATA: | |
| 215 | 0 | prevIndex = index; |
| 216 | ||
| 217 | 0 | if (index == 0) { |
| 218 | // boyer-moore derrived algorithm to safely skip non-boundary data | |
| 219 | 0 | i += boundaryEnd; |
| 220 | 0 | while (i < bufferLength && !(buffer[i] in boundaryChars)) { |
| 221 | 0 | i += boundaryLength; |
| 222 | } | |
| 223 | 0 | i -= boundaryEnd; |
| 224 | 0 | c = buffer[i]; |
| 225 | } | |
| 226 | ||
| 227 | 0 | if (index < boundary.length) { |
| 228 | 0 | if (boundary[index] == c) { |
| 229 | 0 | if (index == 0) { |
| 230 | 0 | dataCallback('partData', true); |
| 231 | } | |
| 232 | 0 | index++; |
| 233 | } else { | |
| 234 | 0 | index = 0; |
| 235 | } | |
| 236 | 0 | } else if (index == boundary.length) { |
| 237 | 0 | index++; |
| 238 | 0 | if (c == CR) { |
| 239 | // CR = part boundary | |
| 240 | 0 | flags |= F.PART_BOUNDARY; |
| 241 | 0 | } else if (c == HYPHEN) { |
| 242 | // HYPHEN = end boundary | |
| 243 | 0 | flags |= F.LAST_BOUNDARY; |
| 244 | } else { | |
| 245 | 0 | index = 0; |
| 246 | } | |
| 247 | 0 | } else if (index - 1 == boundary.length) { |
| 248 | 0 | if (flags & F.PART_BOUNDARY) { |
| 249 | 0 | index = 0; |
| 250 | 0 | if (c == LF) { |
| 251 | // unset the PART_BOUNDARY flag | |
| 252 | 0 | flags &= ~F.PART_BOUNDARY; |
| 253 | 0 | callback('partEnd'); |
| 254 | 0 | callback('partBegin'); |
| 255 | 0 | state = S.HEADER_FIELD_START; |
| 256 | 0 | break; |
| 257 | } | |
| 258 | 0 | } else if (flags & F.LAST_BOUNDARY) { |
| 259 | 0 | if (c == HYPHEN) { |
| 260 | 0 | callback('partEnd'); |
| 261 | 0 | callback('end'); |
| 262 | 0 | state = S.END; |
| 263 | } else { | |
| 264 | 0 | index = 0; |
| 265 | } | |
| 266 | } else { | |
| 267 | 0 | index = 0; |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | 0 | if (index > 0) { |
| 272 | // when matching a possible boundary, keep a lookbehind reference | |
| 273 | // in case it turns out to be a false lead | |
| 274 | 0 | lookbehind[index-1] = c; |
| 275 | 0 | } else if (prevIndex > 0) { |
| 276 | // if our boundary turned out to be rubbish, the captured lookbehind | |
| 277 | // belongs to partData | |
| 278 | 0 | callback('partData', lookbehind, 0, prevIndex); |
| 279 | 0 | prevIndex = 0; |
| 280 | 0 | mark('partData'); |
| 281 | ||
| 282 | // reconsider the current character even so it interrupted the sequence | |
| 283 | // it could be the beginning of a new sequence | |
| 284 | 0 | i--; |
| 285 | } | |
| 286 | ||
| 287 | 0 | break; |
| 288 | case S.END: | |
| 289 | 0 | break; |
| 290 | default: | |
| 291 | 0 | return i; |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | 0 | dataCallback('headerField'); |
| 296 | 0 | dataCallback('headerValue'); |
| 297 | 0 | dataCallback('partData'); |
| 298 | ||
| 299 | 0 | this.index = index; |
| 300 | 0 | this.state = state; |
| 301 | 0 | this.flags = flags; |
| 302 | ||
| 303 | 0 | return len; |
| 304 | }; | |
| 305 | ||
| 306 | 1 | MultipartParser.prototype.end = function() { |
| 307 | 0 | var callback = function(self, name) { |
| 308 | 0 | var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); |
| 309 | 0 | if (callbackSymbol in self) { |
| 310 | 0 | self[callbackSymbol](); |
| 311 | } | |
| 312 | }; | |
| 313 | 0 | if ((this.state == S.HEADER_FIELD_START && this.index == 0) || |
| 314 | (this.state == S.PART_DATA && this.index == this.boundary.length)) { | |
| 315 | 0 | callback(this, 'partEnd'); |
| 316 | 0 | callback(this, 'end'); |
| 317 | 0 | } else if (this.state != S.END) { |
| 318 | 0 | return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain()); |
| 319 | } | |
| 320 | }; | |
| 321 | ||
| 322 | 1 | MultipartParser.prototype.explain = function() { |
| 323 | 0 | return 'state = ' + MultipartParser.stateToString(this.state); |
| 324 | }; | |
| 325 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var EventEmitter = require('events').EventEmitter |
| 2 | , util = require('util'); | |
| 3 | ||
| 4 | 1 | function OctetParser(options){ |
| 5 | 0 | if(!(this instanceof OctetParser)) return new OctetParser(options); |
| 6 | 0 | EventEmitter.call(this); |
| 7 | } | |
| 8 | ||
| 9 | 1 | util.inherits(OctetParser, EventEmitter); |
| 10 | ||
| 11 | 1 | exports.OctetParser = OctetParser; |
| 12 | ||
| 13 | 1 | OctetParser.prototype.write = function(buffer) { |
| 14 | 0 | this.emit('data', buffer); |
| 15 | 0 | return buffer.length; |
| 16 | }; | |
| 17 | ||
| 18 | 1 | OctetParser.prototype.end = function() { |
| 19 | 0 | this.emit('end'); |
| 20 | }; | |
| 21 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | // This is a buffering parser, not quite as nice as the multipart one. | |
| 4 | // If I find time I'll rewrite this to be fully streaming as well | |
| 5 | 1 | var querystring = require('querystring'); |
| 6 | ||
| 7 | 1 | function QuerystringParser(maxKeys) { |
| 8 | 0 | this.maxKeys = maxKeys; |
| 9 | 0 | this.buffer = ''; |
| 10 | }; | |
| 11 | 1 | exports.QuerystringParser = QuerystringParser; |
| 12 | ||
| 13 | 1 | QuerystringParser.prototype.write = function(buffer) { |
| 14 | 0 | this.buffer += buffer.toString('ascii'); |
| 15 | 0 | return buffer.length; |
| 16 | }; | |
| 17 | ||
| 18 | 1 | QuerystringParser.prototype.end = function() { |
| 19 | 0 | var fields = querystring.parse(this.buffer, '&', '=', { maxKeys: this.maxKeys }); |
| 20 | 0 | for (var field in fields) { |
| 21 | 0 | this.onField(field, fields[field]); |
| 22 | } | |
| 23 | 0 | this.buffer = ''; |
| 24 | ||
| 25 | 0 | this.onEnd(); |
| 26 | }; | |
| 27 | ||
| 28 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*global setImmediate: false, setTimeout: false, console: false */ | |
| 2 | 1 | (function () { |
| 3 | ||
| 4 | 1 | var async = {}; |
| 5 | ||
| 6 | // global on the server, window in the browser | |
| 7 | 1 | var root, previous_async; |
| 8 | ||
| 9 | 1 | root = this; |
| 10 | 1 | if (root != null) { |
| 11 | 1 | previous_async = root.async; |
| 12 | } | |
| 13 | ||
| 14 | 1 | async.noConflict = function () { |
| 15 | 0 | root.async = previous_async; |
| 16 | 0 | return async; |
| 17 | }; | |
| 18 | ||
| 19 | 1 | function only_once(fn) { |
| 20 | 0 | var called = false; |
| 21 | 0 | return function() { |
| 22 | 0 | if (called) throw new Error("Callback was already called."); |
| 23 | 0 | called = true; |
| 24 | 0 | fn.apply(root, arguments); |
| 25 | } | |
| 26 | } | |
| 27 | ||
| 28 | //// cross-browser compatiblity functions //// | |
| 29 | ||
| 30 | 1 | var _each = function (arr, iterator) { |
| 31 | 0 | if (arr.forEach) { |
| 32 | 0 | return arr.forEach(iterator); |
| 33 | } | |
| 34 | 0 | for (var i = 0; i < arr.length; i += 1) { |
| 35 | 0 | iterator(arr[i], i, arr); |
| 36 | } | |
| 37 | }; | |
| 38 | ||
| 39 | 1 | var _map = function (arr, iterator) { |
| 40 | 0 | if (arr.map) { |
| 41 | 0 | return arr.map(iterator); |
| 42 | } | |
| 43 | 0 | var results = []; |
| 44 | 0 | _each(arr, function (x, i, a) { |
| 45 | 0 | results.push(iterator(x, i, a)); |
| 46 | }); | |
| 47 | 0 | return results; |
| 48 | }; | |
| 49 | ||
| 50 | 1 | var _reduce = function (arr, iterator, memo) { |
| 51 | 0 | if (arr.reduce) { |
| 52 | 0 | return arr.reduce(iterator, memo); |
| 53 | } | |
| 54 | 0 | _each(arr, function (x, i, a) { |
| 55 | 0 | memo = iterator(memo, x, i, a); |
| 56 | }); | |
| 57 | 0 | return memo; |
| 58 | }; | |
| 59 | ||
| 60 | 1 | var _keys = function (obj) { |
| 61 | 0 | if (Object.keys) { |
| 62 | 0 | return Object.keys(obj); |
| 63 | } | |
| 64 | 0 | var keys = []; |
| 65 | 0 | for (var k in obj) { |
| 66 | 0 | if (obj.hasOwnProperty(k)) { |
| 67 | 0 | keys.push(k); |
| 68 | } | |
| 69 | } | |
| 70 | 0 | return keys; |
| 71 | }; | |
| 72 | ||
| 73 | //// exported async module functions //// | |
| 74 | ||
| 75 | //// nextTick implementation with browser-compatible fallback //// | |
| 76 | 1 | if (typeof process === 'undefined' || !(process.nextTick)) { |
| 77 | 0 | if (typeof setImmediate === 'function') { |
| 78 | 0 | async.nextTick = function (fn) { |
| 79 | // not a direct alias for IE10 compatibility | |
| 80 | 0 | setImmediate(fn); |
| 81 | }; | |
| 82 | 0 | async.setImmediate = async.nextTick; |
| 83 | } | |
| 84 | else { | |
| 85 | 0 | async.nextTick = function (fn) { |
| 86 | 0 | setTimeout(fn, 0); |
| 87 | }; | |
| 88 | 0 | async.setImmediate = async.nextTick; |
| 89 | } | |
| 90 | } | |
| 91 | else { | |
| 92 | 1 | async.nextTick = process.nextTick; |
| 93 | 1 | if (typeof setImmediate !== 'undefined') { |
| 94 | 1 | async.setImmediate = function (fn) { |
| 95 | // not a direct alias for IE10 compatibility | |
| 96 | 0 | setImmediate(fn); |
| 97 | }; | |
| 98 | } | |
| 99 | else { | |
| 100 | 0 | async.setImmediate = async.nextTick; |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | 1 | async.each = function (arr, iterator, callback) { |
| 105 | 0 | callback = callback || function () {}; |
| 106 | 0 | if (!arr.length) { |
| 107 | 0 | return callback(); |
| 108 | } | |
| 109 | 0 | var completed = 0; |
| 110 | 0 | _each(arr, function (x) { |
| 111 | 0 | iterator(x, only_once(function (err) { |
| 112 | 0 | if (err) { |
| 113 | 0 | callback(err); |
| 114 | 0 | callback = function () {}; |
| 115 | } | |
| 116 | else { | |
| 117 | 0 | completed += 1; |
| 118 | 0 | if (completed >= arr.length) { |
| 119 | 0 | callback(null); |
| 120 | } | |
| 121 | } | |
| 122 | })); | |
| 123 | }); | |
| 124 | }; | |
| 125 | 1 | async.forEach = async.each; |
| 126 | ||
| 127 | 1 | async.eachSeries = function (arr, iterator, callback) { |
| 128 | 0 | callback = callback || function () {}; |
| 129 | 0 | if (!arr.length) { |
| 130 | 0 | return callback(); |
| 131 | } | |
| 132 | 0 | var completed = 0; |
| 133 | 0 | var iterate = function () { |
| 134 | 0 | iterator(arr[completed], function (err) { |
| 135 | 0 | if (err) { |
| 136 | 0 | callback(err); |
| 137 | 0 | callback = function () {}; |
| 138 | } | |
| 139 | else { | |
| 140 | 0 | completed += 1; |
| 141 | 0 | if (completed >= arr.length) { |
| 142 | 0 | callback(null); |
| 143 | } | |
| 144 | else { | |
| 145 | 0 | iterate(); |
| 146 | } | |
| 147 | } | |
| 148 | }); | |
| 149 | }; | |
| 150 | 0 | iterate(); |
| 151 | }; | |
| 152 | 1 | async.forEachSeries = async.eachSeries; |
| 153 | ||
| 154 | 1 | async.eachLimit = function (arr, limit, iterator, callback) { |
| 155 | 0 | var fn = _eachLimit(limit); |
| 156 | 0 | fn.apply(null, [arr, iterator, callback]); |
| 157 | }; | |
| 158 | 1 | async.forEachLimit = async.eachLimit; |
| 159 | ||
| 160 | 1 | var _eachLimit = function (limit) { |
| 161 | ||
| 162 | 0 | return function (arr, iterator, callback) { |
| 163 | 0 | callback = callback || function () {}; |
| 164 | 0 | if (!arr.length || limit <= 0) { |
| 165 | 0 | return callback(); |
| 166 | } | |
| 167 | 0 | var completed = 0; |
| 168 | 0 | var started = 0; |
| 169 | 0 | var running = 0; |
| 170 | ||
| 171 | 0 | (function replenish () { |
| 172 | 0 | if (completed >= arr.length) { |
| 173 | 0 | return callback(); |
| 174 | } | |
| 175 | ||
| 176 | 0 | while (running < limit && started < arr.length) { |
| 177 | 0 | started += 1; |
| 178 | 0 | running += 1; |
| 179 | 0 | iterator(arr[started - 1], function (err) { |
| 180 | 0 | if (err) { |
| 181 | 0 | callback(err); |
| 182 | 0 | callback = function () {}; |
| 183 | } | |
| 184 | else { | |
| 185 | 0 | completed += 1; |
| 186 | 0 | running -= 1; |
| 187 | 0 | if (completed >= arr.length) { |
| 188 | 0 | callback(); |
| 189 | } | |
| 190 | else { | |
| 191 | 0 | replenish(); |
| 192 | } | |
| 193 | } | |
| 194 | }); | |
| 195 | } | |
| 196 | })(); | |
| 197 | }; | |
| 198 | }; | |
| 199 | ||
| 200 | ||
| 201 | 1 | var doParallel = function (fn) { |
| 202 | 6 | return function () { |
| 203 | 0 | var args = Array.prototype.slice.call(arguments); |
| 204 | 0 | return fn.apply(null, [async.each].concat(args)); |
| 205 | }; | |
| 206 | }; | |
| 207 | 1 | var doParallelLimit = function(limit, fn) { |
| 208 | 0 | return function () { |
| 209 | 0 | var args = Array.prototype.slice.call(arguments); |
| 210 | 0 | return fn.apply(null, [_eachLimit(limit)].concat(args)); |
| 211 | }; | |
| 212 | }; | |
| 213 | 1 | var doSeries = function (fn) { |
| 214 | 6 | return function () { |
| 215 | 0 | var args = Array.prototype.slice.call(arguments); |
| 216 | 0 | return fn.apply(null, [async.eachSeries].concat(args)); |
| 217 | }; | |
| 218 | }; | |
| 219 | ||
| 220 | ||
| 221 | 1 | var _asyncMap = function (eachfn, arr, iterator, callback) { |
| 222 | 0 | var results = []; |
| 223 | 0 | arr = _map(arr, function (x, i) { |
| 224 | 0 | return {index: i, value: x}; |
| 225 | }); | |
| 226 | 0 | eachfn(arr, function (x, callback) { |
| 227 | 0 | iterator(x.value, function (err, v) { |
| 228 | 0 | results[x.index] = v; |
| 229 | 0 | callback(err); |
| 230 | }); | |
| 231 | }, function (err) { | |
| 232 | 0 | callback(err, results); |
| 233 | }); | |
| 234 | }; | |
| 235 | 1 | async.map = doParallel(_asyncMap); |
| 236 | 1 | async.mapSeries = doSeries(_asyncMap); |
| 237 | 1 | async.mapLimit = function (arr, limit, iterator, callback) { |
| 238 | 0 | return _mapLimit(limit)(arr, iterator, callback); |
| 239 | }; | |
| 240 | ||
| 241 | 1 | var _mapLimit = function(limit) { |
| 242 | 0 | return doParallelLimit(limit, _asyncMap); |
| 243 | }; | |
| 244 | ||
| 245 | // reduce only has a series version, as doing reduce in parallel won't | |
| 246 | // work in many situations. | |
| 247 | 1 | async.reduce = function (arr, memo, iterator, callback) { |
| 248 | 0 | async.eachSeries(arr, function (x, callback) { |
| 249 | 0 | iterator(memo, x, function (err, v) { |
| 250 | 0 | memo = v; |
| 251 | 0 | callback(err); |
| 252 | }); | |
| 253 | }, function (err) { | |
| 254 | 0 | callback(err, memo); |
| 255 | }); | |
| 256 | }; | |
| 257 | // inject alias | |
| 258 | 1 | async.inject = async.reduce; |
| 259 | // foldl alias | |
| 260 | 1 | async.foldl = async.reduce; |
| 261 | ||
| 262 | 1 | async.reduceRight = function (arr, memo, iterator, callback) { |
| 263 | 0 | var reversed = _map(arr, function (x) { |
| 264 | 0 | return x; |
| 265 | }).reverse(); | |
| 266 | 0 | async.reduce(reversed, memo, iterator, callback); |
| 267 | }; | |
| 268 | // foldr alias | |
| 269 | 1 | async.foldr = async.reduceRight; |
| 270 | ||
| 271 | 1 | var _filter = function (eachfn, arr, iterator, callback) { |
| 272 | 0 | var results = []; |
| 273 | 0 | arr = _map(arr, function (x, i) { |
| 274 | 0 | return {index: i, value: x}; |
| 275 | }); | |
| 276 | 0 | eachfn(arr, function (x, callback) { |
| 277 | 0 | iterator(x.value, function (v) { |
| 278 | 0 | if (v) { |
| 279 | 0 | results.push(x); |
| 280 | } | |
| 281 | 0 | callback(); |
| 282 | }); | |
| 283 | }, function (err) { | |
| 284 | 0 | callback(_map(results.sort(function (a, b) { |
| 285 | 0 | return a.index - b.index; |
| 286 | }), function (x) { | |
| 287 | 0 | return x.value; |
| 288 | })); | |
| 289 | }); | |
| 290 | }; | |
| 291 | 1 | async.filter = doParallel(_filter); |
| 292 | 1 | async.filterSeries = doSeries(_filter); |
| 293 | // select alias | |
| 294 | 1 | async.select = async.filter; |
| 295 | 1 | async.selectSeries = async.filterSeries; |
| 296 | ||
| 297 | 1 | var _reject = function (eachfn, arr, iterator, callback) { |
| 298 | 0 | var results = []; |
| 299 | 0 | arr = _map(arr, function (x, i) { |
| 300 | 0 | return {index: i, value: x}; |
| 301 | }); | |
| 302 | 0 | eachfn(arr, function (x, callback) { |
| 303 | 0 | iterator(x.value, function (v) { |
| 304 | 0 | if (!v) { |
| 305 | 0 | results.push(x); |
| 306 | } | |
| 307 | 0 | callback(); |
| 308 | }); | |
| 309 | }, function (err) { | |
| 310 | 0 | callback(_map(results.sort(function (a, b) { |
| 311 | 0 | return a.index - b.index; |
| 312 | }), function (x) { | |
| 313 | 0 | return x.value; |
| 314 | })); | |
| 315 | }); | |
| 316 | }; | |
| 317 | 1 | async.reject = doParallel(_reject); |
| 318 | 1 | async.rejectSeries = doSeries(_reject); |
| 319 | ||
| 320 | 1 | var _detect = function (eachfn, arr, iterator, main_callback) { |
| 321 | 0 | eachfn(arr, function (x, callback) { |
| 322 | 0 | iterator(x, function (result) { |
| 323 | 0 | if (result) { |
| 324 | 0 | main_callback(x); |
| 325 | 0 | main_callback = function () {}; |
| 326 | } | |
| 327 | else { | |
| 328 | 0 | callback(); |
| 329 | } | |
| 330 | }); | |
| 331 | }, function (err) { | |
| 332 | 0 | main_callback(); |
| 333 | }); | |
| 334 | }; | |
| 335 | 1 | async.detect = doParallel(_detect); |
| 336 | 1 | async.detectSeries = doSeries(_detect); |
| 337 | ||
| 338 | 1 | async.some = function (arr, iterator, main_callback) { |
| 339 | 0 | async.each(arr, function (x, callback) { |
| 340 | 0 | iterator(x, function (v) { |
| 341 | 0 | if (v) { |
| 342 | 0 | main_callback(true); |
| 343 | 0 | main_callback = function () {}; |
| 344 | } | |
| 345 | 0 | callback(); |
| 346 | }); | |
| 347 | }, function (err) { | |
| 348 | 0 | main_callback(false); |
| 349 | }); | |
| 350 | }; | |
| 351 | // any alias | |
| 352 | 1 | async.any = async.some; |
| 353 | ||
| 354 | 1 | async.every = function (arr, iterator, main_callback) { |
| 355 | 0 | async.each(arr, function (x, callback) { |
| 356 | 0 | iterator(x, function (v) { |
| 357 | 0 | if (!v) { |
| 358 | 0 | main_callback(false); |
| 359 | 0 | main_callback = function () {}; |
| 360 | } | |
| 361 | 0 | callback(); |
| 362 | }); | |
| 363 | }, function (err) { | |
| 364 | 0 | main_callback(true); |
| 365 | }); | |
| 366 | }; | |
| 367 | // all alias | |
| 368 | 1 | async.all = async.every; |
| 369 | ||
| 370 | 1 | async.sortBy = function (arr, iterator, callback) { |
| 371 | 0 | async.map(arr, function (x, callback) { |
| 372 | 0 | iterator(x, function (err, criteria) { |
| 373 | 0 | if (err) { |
| 374 | 0 | callback(err); |
| 375 | } | |
| 376 | else { | |
| 377 | 0 | callback(null, {value: x, criteria: criteria}); |
| 378 | } | |
| 379 | }); | |
| 380 | }, function (err, results) { | |
| 381 | 0 | if (err) { |
| 382 | 0 | return callback(err); |
| 383 | } | |
| 384 | else { | |
| 385 | 0 | var fn = function (left, right) { |
| 386 | 0 | var a = left.criteria, b = right.criteria; |
| 387 | 0 | return a < b ? -1 : a > b ? 1 : 0; |
| 388 | }; | |
| 389 | 0 | callback(null, _map(results.sort(fn), function (x) { |
| 390 | 0 | return x.value; |
| 391 | })); | |
| 392 | } | |
| 393 | }); | |
| 394 | }; | |
| 395 | ||
| 396 | 1 | async.auto = function (tasks, callback) { |
| 397 | 0 | callback = callback || function () {}; |
| 398 | 0 | var keys = _keys(tasks); |
| 399 | 0 | if (!keys.length) { |
| 400 | 0 | return callback(null); |
| 401 | } | |
| 402 | ||
| 403 | 0 | var results = {}; |
| 404 | ||
| 405 | 0 | var listeners = []; |
| 406 | 0 | var addListener = function (fn) { |
| 407 | 0 | listeners.unshift(fn); |
| 408 | }; | |
| 409 | 0 | var removeListener = function (fn) { |
| 410 | 0 | for (var i = 0; i < listeners.length; i += 1) { |
| 411 | 0 | if (listeners[i] === fn) { |
| 412 | 0 | listeners.splice(i, 1); |
| 413 | 0 | return; |
| 414 | } | |
| 415 | } | |
| 416 | }; | |
| 417 | 0 | var taskComplete = function () { |
| 418 | 0 | _each(listeners.slice(0), function (fn) { |
| 419 | 0 | fn(); |
| 420 | }); | |
| 421 | }; | |
| 422 | ||
| 423 | 0 | addListener(function () { |
| 424 | 0 | if (_keys(results).length === keys.length) { |
| 425 | 0 | callback(null, results); |
| 426 | 0 | callback = function () {}; |
| 427 | } | |
| 428 | }); | |
| 429 | ||
| 430 | 0 | _each(keys, function (k) { |
| 431 | 0 | var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; |
| 432 | 0 | var taskCallback = function (err) { |
| 433 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 434 | 0 | if (args.length <= 1) { |
| 435 | 0 | args = args[0]; |
| 436 | } | |
| 437 | 0 | if (err) { |
| 438 | 0 | var safeResults = {}; |
| 439 | 0 | _each(_keys(results), function(rkey) { |
| 440 | 0 | safeResults[rkey] = results[rkey]; |
| 441 | }); | |
| 442 | 0 | safeResults[k] = args; |
| 443 | 0 | callback(err, safeResults); |
| 444 | // stop subsequent errors hitting callback multiple times | |
| 445 | 0 | callback = function () {}; |
| 446 | } | |
| 447 | else { | |
| 448 | 0 | results[k] = args; |
| 449 | 0 | async.setImmediate(taskComplete); |
| 450 | } | |
| 451 | }; | |
| 452 | 0 | var requires = task.slice(0, Math.abs(task.length - 1)) || []; |
| 453 | 0 | var ready = function () { |
| 454 | 0 | return _reduce(requires, function (a, x) { |
| 455 | 0 | return (a && results.hasOwnProperty(x)); |
| 456 | }, true) && !results.hasOwnProperty(k); | |
| 457 | }; | |
| 458 | 0 | if (ready()) { |
| 459 | 0 | task[task.length - 1](taskCallback, results); |
| 460 | } | |
| 461 | else { | |
| 462 | 0 | var listener = function () { |
| 463 | 0 | if (ready()) { |
| 464 | 0 | removeListener(listener); |
| 465 | 0 | task[task.length - 1](taskCallback, results); |
| 466 | } | |
| 467 | }; | |
| 468 | 0 | addListener(listener); |
| 469 | } | |
| 470 | }); | |
| 471 | }; | |
| 472 | ||
| 473 | 1 | async.waterfall = function (tasks, callback) { |
| 474 | 0 | callback = callback || function () {}; |
| 475 | 0 | if (tasks.constructor !== Array) { |
| 476 | 0 | var err = new Error('First argument to waterfall must be an array of functions'); |
| 477 | 0 | return callback(err); |
| 478 | } | |
| 479 | 0 | if (!tasks.length) { |
| 480 | 0 | return callback(); |
| 481 | } | |
| 482 | 0 | var wrapIterator = function (iterator) { |
| 483 | 0 | return function (err) { |
| 484 | 0 | if (err) { |
| 485 | 0 | callback.apply(null, arguments); |
| 486 | 0 | callback = function () {}; |
| 487 | } | |
| 488 | else { | |
| 489 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 490 | 0 | var next = iterator.next(); |
| 491 | 0 | if (next) { |
| 492 | 0 | args.push(wrapIterator(next)); |
| 493 | } | |
| 494 | else { | |
| 495 | 0 | args.push(callback); |
| 496 | } | |
| 497 | 0 | async.setImmediate(function () { |
| 498 | 0 | iterator.apply(null, args); |
| 499 | }); | |
| 500 | } | |
| 501 | }; | |
| 502 | }; | |
| 503 | 0 | wrapIterator(async.iterator(tasks))(); |
| 504 | }; | |
| 505 | ||
| 506 | 1 | var _parallel = function(eachfn, tasks, callback) { |
| 507 | 0 | callback = callback || function () {}; |
| 508 | 0 | if (tasks.constructor === Array) { |
| 509 | 0 | eachfn.map(tasks, function (fn, callback) { |
| 510 | 0 | if (fn) { |
| 511 | 0 | fn(function (err) { |
| 512 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 513 | 0 | if (args.length <= 1) { |
| 514 | 0 | args = args[0]; |
| 515 | } | |
| 516 | 0 | callback.call(null, err, args); |
| 517 | }); | |
| 518 | } | |
| 519 | }, callback); | |
| 520 | } | |
| 521 | else { | |
| 522 | 0 | var results = {}; |
| 523 | 0 | eachfn.each(_keys(tasks), function (k, callback) { |
| 524 | 0 | tasks[k](function (err) { |
| 525 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 526 | 0 | if (args.length <= 1) { |
| 527 | 0 | args = args[0]; |
| 528 | } | |
| 529 | 0 | results[k] = args; |
| 530 | 0 | callback(err); |
| 531 | }); | |
| 532 | }, function (err) { | |
| 533 | 0 | callback(err, results); |
| 534 | }); | |
| 535 | } | |
| 536 | }; | |
| 537 | ||
| 538 | 1 | async.parallel = function (tasks, callback) { |
| 539 | 0 | _parallel({ map: async.map, each: async.each }, tasks, callback); |
| 540 | }; | |
| 541 | ||
| 542 | 1 | async.parallelLimit = function(tasks, limit, callback) { |
| 543 | 0 | _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); |
| 544 | }; | |
| 545 | ||
| 546 | 1 | async.series = function (tasks, callback) { |
| 547 | 0 | callback = callback || function () {}; |
| 548 | 0 | if (tasks.constructor === Array) { |
| 549 | 0 | async.mapSeries(tasks, function (fn, callback) { |
| 550 | 0 | if (fn) { |
| 551 | 0 | fn(function (err) { |
| 552 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 553 | 0 | if (args.length <= 1) { |
| 554 | 0 | args = args[0]; |
| 555 | } | |
| 556 | 0 | callback.call(null, err, args); |
| 557 | }); | |
| 558 | } | |
| 559 | }, callback); | |
| 560 | } | |
| 561 | else { | |
| 562 | 0 | var results = {}; |
| 563 | 0 | async.eachSeries(_keys(tasks), function (k, callback) { |
| 564 | 0 | tasks[k](function (err) { |
| 565 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 566 | 0 | if (args.length <= 1) { |
| 567 | 0 | args = args[0]; |
| 568 | } | |
| 569 | 0 | results[k] = args; |
| 570 | 0 | callback(err); |
| 571 | }); | |
| 572 | }, function (err) { | |
| 573 | 0 | callback(err, results); |
| 574 | }); | |
| 575 | } | |
| 576 | }; | |
| 577 | ||
| 578 | 1 | async.iterator = function (tasks) { |
| 579 | 0 | var makeCallback = function (index) { |
| 580 | 0 | var fn = function () { |
| 581 | 0 | if (tasks.length) { |
| 582 | 0 | tasks[index].apply(null, arguments); |
| 583 | } | |
| 584 | 0 | return fn.next(); |
| 585 | }; | |
| 586 | 0 | fn.next = function () { |
| 587 | 0 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; |
| 588 | }; | |
| 589 | 0 | return fn; |
| 590 | }; | |
| 591 | 0 | return makeCallback(0); |
| 592 | }; | |
| 593 | ||
| 594 | 1 | async.apply = function (fn) { |
| 595 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 596 | 0 | return function () { |
| 597 | 0 | return fn.apply( |
| 598 | null, args.concat(Array.prototype.slice.call(arguments)) | |
| 599 | ); | |
| 600 | }; | |
| 601 | }; | |
| 602 | ||
| 603 | 1 | var _concat = function (eachfn, arr, fn, callback) { |
| 604 | 0 | var r = []; |
| 605 | 0 | eachfn(arr, function (x, cb) { |
| 606 | 0 | fn(x, function (err, y) { |
| 607 | 0 | r = r.concat(y || []); |
| 608 | 0 | cb(err); |
| 609 | }); | |
| 610 | }, function (err) { | |
| 611 | 0 | callback(err, r); |
| 612 | }); | |
| 613 | }; | |
| 614 | 1 | async.concat = doParallel(_concat); |
| 615 | 1 | async.concatSeries = doSeries(_concat); |
| 616 | ||
| 617 | 1 | async.whilst = function (test, iterator, callback) { |
| 618 | 0 | if (test()) { |
| 619 | 0 | iterator(function (err) { |
| 620 | 0 | if (err) { |
| 621 | 0 | return callback(err); |
| 622 | } | |
| 623 | 0 | async.whilst(test, iterator, callback); |
| 624 | }); | |
| 625 | } | |
| 626 | else { | |
| 627 | 0 | callback(); |
| 628 | } | |
| 629 | }; | |
| 630 | ||
| 631 | 1 | async.doWhilst = function (iterator, test, callback) { |
| 632 | 0 | iterator(function (err) { |
| 633 | 0 | if (err) { |
| 634 | 0 | return callback(err); |
| 635 | } | |
| 636 | 0 | if (test()) { |
| 637 | 0 | async.doWhilst(iterator, test, callback); |
| 638 | } | |
| 639 | else { | |
| 640 | 0 | callback(); |
| 641 | } | |
| 642 | }); | |
| 643 | }; | |
| 644 | ||
| 645 | 1 | async.until = function (test, iterator, callback) { |
| 646 | 0 | if (!test()) { |
| 647 | 0 | iterator(function (err) { |
| 648 | 0 | if (err) { |
| 649 | 0 | return callback(err); |
| 650 | } | |
| 651 | 0 | async.until(test, iterator, callback); |
| 652 | }); | |
| 653 | } | |
| 654 | else { | |
| 655 | 0 | callback(); |
| 656 | } | |
| 657 | }; | |
| 658 | ||
| 659 | 1 | async.doUntil = function (iterator, test, callback) { |
| 660 | 0 | iterator(function (err) { |
| 661 | 0 | if (err) { |
| 662 | 0 | return callback(err); |
| 663 | } | |
| 664 | 0 | if (!test()) { |
| 665 | 0 | async.doUntil(iterator, test, callback); |
| 666 | } | |
| 667 | else { | |
| 668 | 0 | callback(); |
| 669 | } | |
| 670 | }); | |
| 671 | }; | |
| 672 | ||
| 673 | 1 | async.queue = function (worker, concurrency) { |
| 674 | 0 | if (concurrency === undefined) { |
| 675 | 0 | concurrency = 1; |
| 676 | } | |
| 677 | 0 | function _insert(q, data, pos, callback) { |
| 678 | 0 | if(data.constructor !== Array) { |
| 679 | 0 | data = [data]; |
| 680 | } | |
| 681 | 0 | _each(data, function(task) { |
| 682 | 0 | var item = { |
| 683 | data: task, | |
| 684 | callback: typeof callback === 'function' ? callback : null | |
| 685 | }; | |
| 686 | ||
| 687 | 0 | if (pos) { |
| 688 | 0 | q.tasks.unshift(item); |
| 689 | } else { | |
| 690 | 0 | q.tasks.push(item); |
| 691 | } | |
| 692 | ||
| 693 | 0 | if (q.saturated && q.tasks.length === concurrency) { |
| 694 | 0 | q.saturated(); |
| 695 | } | |
| 696 | 0 | async.setImmediate(q.process); |
| 697 | }); | |
| 698 | } | |
| 699 | ||
| 700 | 0 | var workers = 0; |
| 701 | 0 | var q = { |
| 702 | tasks: [], | |
| 703 | concurrency: concurrency, | |
| 704 | saturated: null, | |
| 705 | empty: null, | |
| 706 | drain: null, | |
| 707 | push: function (data, callback) { | |
| 708 | 0 | _insert(q, data, false, callback); |
| 709 | }, | |
| 710 | unshift: function (data, callback) { | |
| 711 | 0 | _insert(q, data, true, callback); |
| 712 | }, | |
| 713 | process: function () { | |
| 714 | 0 | if (workers < q.concurrency && q.tasks.length) { |
| 715 | 0 | var task = q.tasks.shift(); |
| 716 | 0 | if (q.empty && q.tasks.length === 0) { |
| 717 | 0 | q.empty(); |
| 718 | } | |
| 719 | 0 | workers += 1; |
| 720 | 0 | var next = function () { |
| 721 | 0 | workers -= 1; |
| 722 | 0 | if (task.callback) { |
| 723 | 0 | task.callback.apply(task, arguments); |
| 724 | } | |
| 725 | 0 | if (q.drain && q.tasks.length + workers === 0) { |
| 726 | 0 | q.drain(); |
| 727 | } | |
| 728 | 0 | q.process(); |
| 729 | }; | |
| 730 | 0 | var cb = only_once(next); |
| 731 | 0 | worker(task.data, cb); |
| 732 | } | |
| 733 | }, | |
| 734 | length: function () { | |
| 735 | 0 | return q.tasks.length; |
| 736 | }, | |
| 737 | running: function () { | |
| 738 | 0 | return workers; |
| 739 | } | |
| 740 | }; | |
| 741 | 0 | return q; |
| 742 | }; | |
| 743 | ||
| 744 | 1 | async.cargo = function (worker, payload) { |
| 745 | 0 | var working = false, |
| 746 | tasks = []; | |
| 747 | ||
| 748 | 0 | var cargo = { |
| 749 | tasks: tasks, | |
| 750 | payload: payload, | |
| 751 | saturated: null, | |
| 752 | empty: null, | |
| 753 | drain: null, | |
| 754 | push: function (data, callback) { | |
| 755 | 0 | if(data.constructor !== Array) { |
| 756 | 0 | data = [data]; |
| 757 | } | |
| 758 | 0 | _each(data, function(task) { |
| 759 | 0 | tasks.push({ |
| 760 | data: task, | |
| 761 | callback: typeof callback === 'function' ? callback : null | |
| 762 | }); | |
| 763 | 0 | if (cargo.saturated && tasks.length === payload) { |
| 764 | 0 | cargo.saturated(); |
| 765 | } | |
| 766 | }); | |
| 767 | 0 | async.setImmediate(cargo.process); |
| 768 | }, | |
| 769 | process: function process() { | |
| 770 | 0 | if (working) return; |
| 771 | 0 | if (tasks.length === 0) { |
| 772 | 0 | if(cargo.drain) cargo.drain(); |
| 773 | 0 | return; |
| 774 | } | |
| 775 | ||
| 776 | 0 | var ts = typeof payload === 'number' |
| 777 | ? tasks.splice(0, payload) | |
| 778 | : tasks.splice(0); | |
| 779 | ||
| 780 | 0 | var ds = _map(ts, function (task) { |
| 781 | 0 | return task.data; |
| 782 | }); | |
| 783 | ||
| 784 | 0 | if(cargo.empty) cargo.empty(); |
| 785 | 0 | working = true; |
| 786 | 0 | worker(ds, function () { |
| 787 | 0 | working = false; |
| 788 | ||
| 789 | 0 | var args = arguments; |
| 790 | 0 | _each(ts, function (data) { |
| 791 | 0 | if (data.callback) { |
| 792 | 0 | data.callback.apply(null, args); |
| 793 | } | |
| 794 | }); | |
| 795 | ||
| 796 | 0 | process(); |
| 797 | }); | |
| 798 | }, | |
| 799 | length: function () { | |
| 800 | 0 | return tasks.length; |
| 801 | }, | |
| 802 | running: function () { | |
| 803 | 0 | return working; |
| 804 | } | |
| 805 | }; | |
| 806 | 0 | return cargo; |
| 807 | }; | |
| 808 | ||
| 809 | 1 | var _console_fn = function (name) { |
| 810 | 2 | return function (fn) { |
| 811 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 812 | 0 | fn.apply(null, args.concat([function (err) { |
| 813 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 814 | 0 | if (typeof console !== 'undefined') { |
| 815 | 0 | if (err) { |
| 816 | 0 | if (console.error) { |
| 817 | 0 | console.error(err); |
| 818 | } | |
| 819 | } | |
| 820 | 0 | else if (console[name]) { |
| 821 | 0 | _each(args, function (x) { |
| 822 | 0 | console[name](x); |
| 823 | }); | |
| 824 | } | |
| 825 | } | |
| 826 | }])); | |
| 827 | }; | |
| 828 | }; | |
| 829 | 1 | async.log = _console_fn('log'); |
| 830 | 1 | async.dir = _console_fn('dir'); |
| 831 | /*async.info = _console_fn('info'); | |
| 832 | async.warn = _console_fn('warn'); | |
| 833 | async.error = _console_fn('error');*/ | |
| 834 | ||
| 835 | 1 | async.memoize = function (fn, hasher) { |
| 836 | 0 | var memo = {}; |
| 837 | 0 | var queues = {}; |
| 838 | 0 | hasher = hasher || function (x) { |
| 839 | 0 | return x; |
| 840 | }; | |
| 841 | 0 | var memoized = function () { |
| 842 | 0 | var args = Array.prototype.slice.call(arguments); |
| 843 | 0 | var callback = args.pop(); |
| 844 | 0 | var key = hasher.apply(null, args); |
| 845 | 0 | if (key in memo) { |
| 846 | 0 | callback.apply(null, memo[key]); |
| 847 | } | |
| 848 | 0 | else if (key in queues) { |
| 849 | 0 | queues[key].push(callback); |
| 850 | } | |
| 851 | else { | |
| 852 | 0 | queues[key] = [callback]; |
| 853 | 0 | fn.apply(null, args.concat([function () { |
| 854 | 0 | memo[key] = arguments; |
| 855 | 0 | var q = queues[key]; |
| 856 | 0 | delete queues[key]; |
| 857 | 0 | for (var i = 0, l = q.length; i < l; i++) { |
| 858 | 0 | q[i].apply(null, arguments); |
| 859 | } | |
| 860 | }])); | |
| 861 | } | |
| 862 | }; | |
| 863 | 0 | memoized.memo = memo; |
| 864 | 0 | memoized.unmemoized = fn; |
| 865 | 0 | return memoized; |
| 866 | }; | |
| 867 | ||
| 868 | 1 | async.unmemoize = function (fn) { |
| 869 | 0 | return function () { |
| 870 | 0 | return (fn.unmemoized || fn).apply(null, arguments); |
| 871 | }; | |
| 872 | }; | |
| 873 | ||
| 874 | 1 | async.times = function (count, iterator, callback) { |
| 875 | 0 | var counter = []; |
| 876 | 0 | for (var i = 0; i < count; i++) { |
| 877 | 0 | counter.push(i); |
| 878 | } | |
| 879 | 0 | return async.map(counter, iterator, callback); |
| 880 | }; | |
| 881 | ||
| 882 | 1 | async.timesSeries = function (count, iterator, callback) { |
| 883 | 0 | var counter = []; |
| 884 | 0 | for (var i = 0; i < count; i++) { |
| 885 | 0 | counter.push(i); |
| 886 | } | |
| 887 | 0 | return async.mapSeries(counter, iterator, callback); |
| 888 | }; | |
| 889 | ||
| 890 | 1 | async.compose = function (/* functions... */) { |
| 891 | 0 | var fns = Array.prototype.reverse.call(arguments); |
| 892 | 0 | return function () { |
| 893 | 0 | var that = this; |
| 894 | 0 | var args = Array.prototype.slice.call(arguments); |
| 895 | 0 | var callback = args.pop(); |
| 896 | 0 | async.reduce(fns, args, function (newargs, fn, cb) { |
| 897 | 0 | fn.apply(that, newargs.concat([function () { |
| 898 | 0 | var err = arguments[0]; |
| 899 | 0 | var nextargs = Array.prototype.slice.call(arguments, 1); |
| 900 | 0 | cb(err, nextargs); |
| 901 | }])) | |
| 902 | }, | |
| 903 | function (err, results) { | |
| 904 | 0 | callback.apply(that, [err].concat(results)); |
| 905 | }); | |
| 906 | }; | |
| 907 | }; | |
| 908 | ||
| 909 | 1 | var _applyEach = function (eachfn, fns /*args...*/) { |
| 910 | 0 | var go = function () { |
| 911 | 0 | var that = this; |
| 912 | 0 | var args = Array.prototype.slice.call(arguments); |
| 913 | 0 | var callback = args.pop(); |
| 914 | 0 | return eachfn(fns, function (fn, cb) { |
| 915 | 0 | fn.apply(that, args.concat([cb])); |
| 916 | }, | |
| 917 | callback); | |
| 918 | }; | |
| 919 | 0 | if (arguments.length > 2) { |
| 920 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 921 | 0 | return go.apply(this, args); |
| 922 | } | |
| 923 | else { | |
| 924 | 0 | return go; |
| 925 | } | |
| 926 | }; | |
| 927 | 1 | async.applyEach = doParallel(_applyEach); |
| 928 | 1 | async.applyEachSeries = doSeries(_applyEach); |
| 929 | ||
| 930 | 1 | async.forever = function (fn, callback) { |
| 931 | 0 | function next(err) { |
| 932 | 0 | if (err) { |
| 933 | 0 | if (callback) { |
| 934 | 0 | return callback(err); |
| 935 | } | |
| 936 | 0 | throw err; |
| 937 | } | |
| 938 | 0 | fn(next); |
| 939 | } | |
| 940 | 0 | next(); |
| 941 | }; | |
| 942 | ||
| 943 | // AMD / RequireJS | |
| 944 | 1 | if (typeof define !== 'undefined' && define.amd) { |
| 945 | 0 | define([], function () { |
| 946 | 0 | return async; |
| 947 | }); | |
| 948 | } | |
| 949 | // Node.js | |
| 950 | 1 | else if (typeof module !== 'undefined' && module.exports) { |
| 951 | 1 | module.exports = async; |
| 952 | } | |
| 953 | // included directly via <script> tag | |
| 954 | else { | |
| 955 | 0 | root.async = async; |
| 956 | } | |
| 957 | ||
| 958 | }()); | |
| 959 |
| Line | Hits | Source |
|---|---|---|
| 1 | /***@@@ BEGIN LICENSE @@@***/ | |
| 2 | /*───────────────────────────────────────────────────────────────────────────*\ | |
| 3 | │ Copyright (C) 2013 eBay Software Foundation │ | |
| 4 | │ │ | |
| 5 | │hh ,'""`. │ | |
| 6 | │ / _ _ \ Licensed under the Apache License, Version 2.0 (the "License"); │ | |
| 7 | │ |(@)(@)| you may not use this file except in compliance with the License. │ | |
| 8 | │ ) __ ( You may obtain a copy of the License at │ | |
| 9 | │ /,'))((`.\ │ | |
| 10 | │(( (( )) )) http://www.apache.org/licenses/LICENSE-2.0 │ | |
| 11 | │ `\ `)(' /' │ | |
| 12 | │ │ | |
| 13 | │ Unless required by applicable law or agreed to in writing, software │ | |
| 14 | │ distributed under the License is distributed on an "AS IS" BASIS, │ | |
| 15 | │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ | |
| 16 | │ See the License for the specific language governing permissions and │ | |
| 17 | │ limitations under the License. │ | |
| 18 | \*───────────────────────────────────────────────────────────────────────────*/ | |
| 19 | /***@@@ END LICENSE @@@***/ | |
| 20 | 1 | 'use strict'; |
| 21 | ||
| 22 | ||
| 23 | ||
| 24 | 1 | exports.create = function (parent) { |
| 25 | ||
| 26 | 0 | return { |
| 27 | ||
| 28 | _parent: parent, | |
| 29 | ||
| 30 | _handlers: Object.create(null), | |
| 31 | ||
| 32 | use: function (protocol, impl) { | |
| 33 | 0 | var handlers, handler, index, removed; |
| 34 | ||
| 35 | 0 | handlers = this._handlers; |
| 36 | 0 | handler = handlers[protocol]; |
| 37 | ||
| 38 | 0 | if(!handler) { |
| 39 | 0 | handler = handlers[protocol] = { |
| 40 | ||
| 41 | protocol: protocol, | |
| 42 | ||
| 43 | regex: new RegExp('^' + protocol + ':'), | |
| 44 | ||
| 45 | predicate: function (value) { | |
| 46 | 0 | return this.regex.test(value); |
| 47 | }, | |
| 48 | ||
| 49 | stack: [] | |
| 50 | ||
| 51 | }; | |
| 52 | } | |
| 53 | ||
| 54 | 0 | index = handler.stack.push(impl); |
| 55 | 0 | removed = false; |
| 56 | ||
| 57 | // Unuse | |
| 58 | 0 | return function () { |
| 59 | 0 | if (!removed) { |
| 60 | 0 | removed = true; |
| 61 | 0 | return handler.stack.splice(index - 1, 1)[0]; |
| 62 | } | |
| 63 | 0 | return undefined; |
| 64 | } | |
| 65 | }, | |
| 66 | ||
| 67 | getStack: function (protocol) { | |
| 68 | 0 | var current, parent, hasParent; |
| 69 | ||
| 70 | 0 | current = this._handlers[protocol] && this._handlers[protocol].stack; |
| 71 | 0 | parent = this._parent && this._parent.getStack(protocol); |
| 72 | 0 | hasParent = parent && parent.length; |
| 73 | ||
| 74 | 0 | if (current && hasParent) { |
| 75 | 0 | return current.concat(parent); |
| 76 | } | |
| 77 | ||
| 78 | 0 | if (hasParent) { |
| 79 | 0 | return parent; |
| 80 | } | |
| 81 | ||
| 82 | 0 | return current; |
| 83 | }, | |
| 84 | ||
| 85 | resolve: function resolve(src) { | |
| 86 | 0 | var dest, handlers; |
| 87 | ||
| 88 | 0 | dest = src; |
| 89 | ||
| 90 | 0 | if (typeof src === 'object' && src !== null) { |
| 91 | ||
| 92 | 0 | dest = (Array.isArray(src) ? [] : Object.create(Object.getPrototypeOf(src))); |
| 93 | 0 | Object.keys(src).forEach(function (key) { |
| 94 | 0 | dest[key] = this.resolve(src[key]); |
| 95 | }, this); | |
| 96 | ||
| 97 | 0 | } else if (typeof src === 'string') { |
| 98 | ||
| 99 | 0 | handlers = this._handlers; |
| 100 | 0 | Object.keys(handlers).forEach(function (protocol) { |
| 101 | 0 | var handler = handlers[protocol]; |
| 102 | ||
| 103 | 0 | if (handler.predicate(src)) { |
| 104 | // run through stack and mutate | |
| 105 | 0 | dest = src.slice(protocol.length + 1); |
| 106 | 0 | this.getStack(protocol).forEach(function (handler) { |
| 107 | 0 | dest = handler(dest); |
| 108 | }); | |
| 109 | } | |
| 110 | }, this); | |
| 111 | ||
| 112 | } | |
| 113 | ||
| 114 | 0 | return dest; |
| 115 | } | |
| 116 | ||
| 117 | }; | |
| 118 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Expose `Progress`. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | module.exports = Progress; |
| 6 | ||
| 7 | /** | |
| 8 | * Initialize a new `Progress` indicator. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | function Progress() { |
| 12 | 0 | this.percent = 0; |
| 13 | 0 | this.size(0); |
| 14 | 0 | this.fontSize(11); |
| 15 | 0 | this.font('helvetica, arial, sans-serif'); |
| 16 | } | |
| 17 | ||
| 18 | /** | |
| 19 | * Set progress size to `n`. | |
| 20 | * | |
| 21 | * @param {Number} n | |
| 22 | * @return {Progress} for chaining | |
| 23 | * @api public | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | Progress.prototype.size = function(n){ |
| 27 | 0 | this._size = n; |
| 28 | 0 | return this; |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Set text to `str`. | |
| 33 | * | |
| 34 | * @param {String} str | |
| 35 | * @return {Progress} for chaining | |
| 36 | * @api public | |
| 37 | */ | |
| 38 | ||
| 39 | 1 | Progress.prototype.text = function(str){ |
| 40 | 0 | this._text = str; |
| 41 | 0 | return this; |
| 42 | }; | |
| 43 | ||
| 44 | /** | |
| 45 | * Set font size to `n`. | |
| 46 | * | |
| 47 | * @param {Number} n | |
| 48 | * @return {Progress} for chaining | |
| 49 | * @api public | |
| 50 | */ | |
| 51 | ||
| 52 | 1 | Progress.prototype.fontSize = function(n){ |
| 53 | 0 | this._fontSize = n; |
| 54 | 0 | return this; |
| 55 | }; | |
| 56 | ||
| 57 | /** | |
| 58 | * Set font `family`. | |
| 59 | * | |
| 60 | * @param {String} family | |
| 61 | * @return {Progress} for chaining | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | Progress.prototype.font = function(family){ |
| 65 | 0 | this._font = family; |
| 66 | 0 | return this; |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Update percentage to `n`. | |
| 71 | * | |
| 72 | * @param {Number} n | |
| 73 | * @return {Progress} for chaining | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | Progress.prototype.update = function(n){ |
| 77 | 0 | this.percent = n; |
| 78 | 0 | return this; |
| 79 | }; | |
| 80 | ||
| 81 | /** | |
| 82 | * Draw on `ctx`. | |
| 83 | * | |
| 84 | * @param {CanvasRenderingContext2d} ctx | |
| 85 | * @return {Progress} for chaining | |
| 86 | */ | |
| 87 | ||
| 88 | 1 | Progress.prototype.draw = function(ctx){ |
| 89 | 0 | try { |
| 90 | 0 | var percent = Math.min(this.percent, 100) |
| 91 | , size = this._size | |
| 92 | , half = size / 2 | |
| 93 | , x = half | |
| 94 | , y = half | |
| 95 | , rad = half - 1 | |
| 96 | , fontSize = this._fontSize; | |
| 97 | ||
| 98 | 0 | ctx.font = fontSize + 'px ' + this._font; |
| 99 | ||
| 100 | 0 | var angle = Math.PI * 2 * (percent / 100); |
| 101 | 0 | ctx.clearRect(0, 0, size, size); |
| 102 | ||
| 103 | // outer circle | |
| 104 | 0 | ctx.strokeStyle = '#9f9f9f'; |
| 105 | 0 | ctx.beginPath(); |
| 106 | 0 | ctx.arc(x, y, rad, 0, angle, false); |
| 107 | 0 | ctx.stroke(); |
| 108 | ||
| 109 | // inner circle | |
| 110 | 0 | ctx.strokeStyle = '#eee'; |
| 111 | 0 | ctx.beginPath(); |
| 112 | 0 | ctx.arc(x, y, rad - 1, 0, angle, true); |
| 113 | 0 | ctx.stroke(); |
| 114 | ||
| 115 | // text | |
| 116 | 0 | var text = this._text || (percent | 0) + '%' |
| 117 | , w = ctx.measureText(text).width; | |
| 118 | ||
| 119 | 0 | ctx.fillText( |
| 120 | text | |
| 121 | , x - w / 2 + 1 | |
| 122 | , y + fontSize / 2 - 1); | |
| 123 | } catch (ex) {} //don't fail if we can't render progress | |
| 124 | 0 | return this; |
| 125 | }; | |
| 126 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Expose `Context`. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | module.exports = Context; |
| 7 | ||
| 8 | /** | |
| 9 | * Initialize a new `Context`. | |
| 10 | * | |
| 11 | * @api private | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | function Context(){} |
| 15 | ||
| 16 | /** | |
| 17 | * Set or get the context `Runnable` to `runnable`. | |
| 18 | * | |
| 19 | * @param {Runnable} runnable | |
| 20 | * @return {Context} | |
| 21 | * @api private | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | Context.prototype.runnable = function(runnable){ |
| 25 | 3 | if (0 == arguments.length) return this._runnable; |
| 26 | 3 | this.test = this._runnable = runnable; |
| 27 | 3 | return this; |
| 28 | }; | |
| 29 | ||
| 30 | /** | |
| 31 | * Set test timeout `ms`. | |
| 32 | * | |
| 33 | * @param {Number} ms | |
| 34 | * @return {Context} self | |
| 35 | * @api private | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | Context.prototype.timeout = function(ms){ |
| 39 | 0 | this.runnable().timeout(ms); |
| 40 | 0 | return this; |
| 41 | }; | |
| 42 | ||
| 43 | /** | |
| 44 | * Set test slowness threshold `ms`. | |
| 45 | * | |
| 46 | * @param {Number} ms | |
| 47 | * @return {Context} self | |
| 48 | * @api private | |
| 49 | */ | |
| 50 | ||
| 51 | 1 | Context.prototype.slow = function(ms){ |
| 52 | 0 | this.runnable().slow(ms); |
| 53 | 0 | return this; |
| 54 | }; | |
| 55 | ||
| 56 | /** | |
| 57 | * Inspect the context void of `._runnable`. | |
| 58 | * | |
| 59 | * @return {String} | |
| 60 | * @api private | |
| 61 | */ | |
| 62 | ||
| 63 | 1 | Context.prototype.inspect = function(){ |
| 64 | 0 | return JSON.stringify(this, function(key, val){ |
| 65 | 0 | if ('_runnable' == key) return; |
| 66 | 0 | if ('test' == key) return; |
| 67 | 0 | return val; |
| 68 | }, 2); | |
| 69 | }; | |
| 70 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Runnable = require('./runnable'); |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Hook`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | module.exports = Hook; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Hook` with the given `title` and callback `fn`. | |
| 16 | * | |
| 17 | * @param {String} title | |
| 18 | * @param {Function} fn | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function Hook(title, fn) { |
| 23 | 2 | Runnable.call(this, title, fn); |
| 24 | 2 | this.type = 'hook'; |
| 25 | } | |
| 26 | ||
| 27 | /** | |
| 28 | * Inherit from `Runnable.prototype`. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | Hook.prototype.__proto__ = Runnable.prototype; |
| 32 | ||
| 33 | /** | |
| 34 | * Get or set the test `err`. | |
| 35 | * | |
| 36 | * @param {Error} err | |
| 37 | * @return {Error} | |
| 38 | * @api public | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | Hook.prototype.error = function(err){ |
| 42 | 2 | if (0 == arguments.length) { |
| 43 | 2 | var err = this._error; |
| 44 | 2 | this._error = null; |
| 45 | 2 | return err; |
| 46 | } | |
| 47 | ||
| 48 | 0 | this._error = err; |
| 49 | }; | |
| 50 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Suite = require('../suite') |
| 7 | , Test = require('../test') | |
| 8 | , utils = require('../utils'); | |
| 9 | ||
| 10 | /** | |
| 11 | * BDD-style interface: | |
| 12 | * | |
| 13 | * describe('Array', function(){ | |
| 14 | * describe('#indexOf()', function(){ | |
| 15 | * it('should return -1 when not present', function(){ | |
| 16 | * | |
| 17 | * }); | |
| 18 | * | |
| 19 | * it('should return the index when present', function(){ | |
| 20 | * | |
| 21 | * }); | |
| 22 | * }); | |
| 23 | * }); | |
| 24 | * | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | module.exports = function(suite){ |
| 28 | 2 | var suites = [suite]; |
| 29 | ||
| 30 | 2 | suite.on('pre-require', function(context, file, mocha){ |
| 31 | ||
| 32 | /** | |
| 33 | * Execute before running tests. | |
| 34 | */ | |
| 35 | ||
| 36 | 4 | context.before = function(fn){ |
| 37 | 0 | suites[0].beforeAll(fn); |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * Execute after running tests. | |
| 42 | */ | |
| 43 | ||
| 44 | 4 | context.after = function(fn){ |
| 45 | 0 | suites[0].afterAll(fn); |
| 46 | }; | |
| 47 | ||
| 48 | /** | |
| 49 | * Execute before each test case. | |
| 50 | */ | |
| 51 | ||
| 52 | 4 | context.beforeEach = function(fn){ |
| 53 | 3 | suites[0].beforeEach(fn); |
| 54 | }; | |
| 55 | ||
| 56 | /** | |
| 57 | * Execute after each test case. | |
| 58 | */ | |
| 59 | ||
| 60 | 4 | context.afterEach = function(fn){ |
| 61 | 1 | suites[0].afterEach(fn); |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Describe a "suite" with the given `title` | |
| 66 | * and callback `fn` containing nested suites | |
| 67 | * and/or tests. | |
| 68 | */ | |
| 69 | ||
| 70 | 4 | context.describe = context.context = function(title, fn){ |
| 71 | 2 | var suite = Suite.create(suites[0], title); |
| 72 | 2 | suites.unshift(suite); |
| 73 | 2 | fn.call(suite); |
| 74 | 2 | suites.shift(); |
| 75 | 2 | return suite; |
| 76 | }; | |
| 77 | ||
| 78 | /** | |
| 79 | * Pending describe. | |
| 80 | */ | |
| 81 | ||
| 82 | 4 | context.xdescribe = |
| 83 | context.xcontext = | |
| 84 | context.describe.skip = function(title, fn){ | |
| 85 | 1 | var suite = Suite.create(suites[0], title); |
| 86 | 1 | suite.pending = true; |
| 87 | 1 | suites.unshift(suite); |
| 88 | 1 | fn.call(suite); |
| 89 | 1 | suites.shift(); |
| 90 | }; | |
| 91 | ||
| 92 | /** | |
| 93 | * Exclusive suite. | |
| 94 | */ | |
| 95 | ||
| 96 | 4 | context.describe.only = function(title, fn){ |
| 97 | 0 | var suite = context.describe(title, fn); |
| 98 | 0 | mocha.grep(suite.fullTitle()); |
| 99 | 0 | return suite; |
| 100 | }; | |
| 101 | ||
| 102 | /** | |
| 103 | * Describe a specification or test-case | |
| 104 | * with the given `title` and callback `fn` | |
| 105 | * acting as a thunk. | |
| 106 | */ | |
| 107 | ||
| 108 | 4 | context.it = context.specify = function(title, fn){ |
| 109 | 2 | var suite = suites[0]; |
| 110 | 3 | if (suite.pending) var fn = null; |
| 111 | 2 | var test = new Test(title, fn); |
| 112 | 2 | suite.addTest(test); |
| 113 | 2 | return test; |
| 114 | }; | |
| 115 | ||
| 116 | /** | |
| 117 | * Exclusive test-case. | |
| 118 | */ | |
| 119 | ||
| 120 | 4 | context.it.only = function(title, fn){ |
| 121 | 0 | var test = context.it(title, fn); |
| 122 | 0 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; |
| 123 | 0 | mocha.grep(new RegExp(reString)); |
| 124 | 0 | return test; |
| 125 | }; | |
| 126 | ||
| 127 | /** | |
| 128 | * Pending test case. | |
| 129 | */ | |
| 130 | ||
| 131 | 4 | context.xit = |
| 132 | context.xspecify = | |
| 133 | context.it.skip = function(title){ | |
| 134 | 0 | context.it(title); |
| 135 | }; | |
| 136 | }); | |
| 137 | }; | |
| 138 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Suite = require('../suite') |
| 7 | , Test = require('../test'); | |
| 8 | ||
| 9 | /** | |
| 10 | * TDD-style interface: | |
| 11 | * | |
| 12 | * exports.Array = { | |
| 13 | * '#indexOf()': { | |
| 14 | * 'should return -1 when the value is not present': function(){ | |
| 15 | * | |
| 16 | * }, | |
| 17 | * | |
| 18 | * 'should return the correct index when the value is present': function(){ | |
| 19 | * | |
| 20 | * } | |
| 21 | * } | |
| 22 | * }; | |
| 23 | * | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | module.exports = function(suite){ |
| 27 | 0 | var suites = [suite]; |
| 28 | ||
| 29 | 0 | suite.on('require', visit); |
| 30 | ||
| 31 | 0 | function visit(obj) { |
| 32 | 0 | var suite; |
| 33 | 0 | for (var key in obj) { |
| 34 | 0 | if ('function' == typeof obj[key]) { |
| 35 | 0 | var fn = obj[key]; |
| 36 | 0 | switch (key) { |
| 37 | case 'before': | |
| 38 | 0 | suites[0].beforeAll(fn); |
| 39 | 0 | break; |
| 40 | case 'after': | |
| 41 | 0 | suites[0].afterAll(fn); |
| 42 | 0 | break; |
| 43 | case 'beforeEach': | |
| 44 | 0 | suites[0].beforeEach(fn); |
| 45 | 0 | break; |
| 46 | case 'afterEach': | |
| 47 | 0 | suites[0].afterEach(fn); |
| 48 | 0 | break; |
| 49 | default: | |
| 50 | 0 | suites[0].addTest(new Test(key, fn)); |
| 51 | } | |
| 52 | } else { | |
| 53 | 0 | var suite = Suite.create(suites[0], key); |
| 54 | 0 | suites.unshift(suite); |
| 55 | 0 | visit(obj[key]); |
| 56 | 0 | suites.shift(); |
| 57 | } | |
| 58 | } | |
| 59 | } | |
| 60 | }; | |
| 61 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | 1 | exports.bdd = require('./bdd'); |
| 3 | 1 | exports.tdd = require('./tdd'); |
| 4 | 1 | exports.qunit = require('./qunit'); |
| 5 | 1 | exports.exports = require('./exports'); |
| 6 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Suite = require('../suite') |
| 7 | , Test = require('../test') | |
| 8 | , utils = require('../utils'); | |
| 9 | ||
| 10 | /** | |
| 11 | * QUnit-style interface: | |
| 12 | * | |
| 13 | * suite('Array'); | |
| 14 | * | |
| 15 | * test('#length', function(){ | |
| 16 | * var arr = [1,2,3]; | |
| 17 | * ok(arr.length == 3); | |
| 18 | * }); | |
| 19 | * | |
| 20 | * test('#indexOf()', function(){ | |
| 21 | * var arr = [1,2,3]; | |
| 22 | * ok(arr.indexOf(1) == 0); | |
| 23 | * ok(arr.indexOf(2) == 1); | |
| 24 | * ok(arr.indexOf(3) == 2); | |
| 25 | * }); | |
| 26 | * | |
| 27 | * suite('String'); | |
| 28 | * | |
| 29 | * test('#length', function(){ | |
| 30 | * ok('foo'.length == 3); | |
| 31 | * }); | |
| 32 | * | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | module.exports = function(suite){ |
| 36 | 0 | var suites = [suite]; |
| 37 | ||
| 38 | 0 | suite.on('pre-require', function(context, file, mocha){ |
| 39 | ||
| 40 | /** | |
| 41 | * Execute before running tests. | |
| 42 | */ | |
| 43 | ||
| 44 | 0 | context.before = function(fn){ |
| 45 | 0 | suites[0].beforeAll(fn); |
| 46 | }; | |
| 47 | ||
| 48 | /** | |
| 49 | * Execute after running tests. | |
| 50 | */ | |
| 51 | ||
| 52 | 0 | context.after = function(fn){ |
| 53 | 0 | suites[0].afterAll(fn); |
| 54 | }; | |
| 55 | ||
| 56 | /** | |
| 57 | * Execute before each test case. | |
| 58 | */ | |
| 59 | ||
| 60 | 0 | context.beforeEach = function(fn){ |
| 61 | 0 | suites[0].beforeEach(fn); |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Execute after each test case. | |
| 66 | */ | |
| 67 | ||
| 68 | 0 | context.afterEach = function(fn){ |
| 69 | 0 | suites[0].afterEach(fn); |
| 70 | }; | |
| 71 | ||
| 72 | /** | |
| 73 | * Describe a "suite" with the given `title`. | |
| 74 | */ | |
| 75 | ||
| 76 | 0 | context.suite = function(title){ |
| 77 | 0 | if (suites.length > 1) suites.shift(); |
| 78 | 0 | var suite = Suite.create(suites[0], title); |
| 79 | 0 | suites.unshift(suite); |
| 80 | 0 | return suite; |
| 81 | }; | |
| 82 | ||
| 83 | /** | |
| 84 | * Exclusive test-case. | |
| 85 | */ | |
| 86 | ||
| 87 | 0 | context.suite.only = function(title, fn){ |
| 88 | 0 | var suite = context.suite(title, fn); |
| 89 | 0 | mocha.grep(suite.fullTitle()); |
| 90 | }; | |
| 91 | ||
| 92 | /** | |
| 93 | * Describe a specification or test-case | |
| 94 | * with the given `title` and callback `fn` | |
| 95 | * acting as a thunk. | |
| 96 | */ | |
| 97 | ||
| 98 | 0 | context.test = function(title, fn){ |
| 99 | 0 | var test = new Test(title, fn); |
| 100 | 0 | suites[0].addTest(test); |
| 101 | 0 | return test; |
| 102 | }; | |
| 103 | ||
| 104 | /** | |
| 105 | * Exclusive test-case. | |
| 106 | */ | |
| 107 | ||
| 108 | 0 | context.test.only = function(title, fn){ |
| 109 | 0 | var test = context.test(title, fn); |
| 110 | 0 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; |
| 111 | 0 | mocha.grep(new RegExp(reString)); |
| 112 | }; | |
| 113 | ||
| 114 | /** | |
| 115 | * Pending test case. | |
| 116 | */ | |
| 117 | ||
| 118 | 0 | context.test.skip = function(title){ |
| 119 | 0 | context.test(title); |
| 120 | }; | |
| 121 | }); | |
| 122 | }; | |
| 123 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Suite = require('../suite') |
| 7 | , Test = require('../test') | |
| 8 | , utils = require('../utils');; | |
| 9 | ||
| 10 | /** | |
| 11 | * TDD-style interface: | |
| 12 | * | |
| 13 | * suite('Array', function(){ | |
| 14 | * suite('#indexOf()', function(){ | |
| 15 | * suiteSetup(function(){ | |
| 16 | * | |
| 17 | * }); | |
| 18 | * | |
| 19 | * test('should return -1 when not present', function(){ | |
| 20 | * | |
| 21 | * }); | |
| 22 | * | |
| 23 | * test('should return the index when present', function(){ | |
| 24 | * | |
| 25 | * }); | |
| 26 | * | |
| 27 | * suiteTeardown(function(){ | |
| 28 | * | |
| 29 | * }); | |
| 30 | * }); | |
| 31 | * }); | |
| 32 | * | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | module.exports = function(suite){ |
| 36 | 0 | var suites = [suite]; |
| 37 | ||
| 38 | 0 | suite.on('pre-require', function(context, file, mocha){ |
| 39 | ||
| 40 | /** | |
| 41 | * Execute before each test case. | |
| 42 | */ | |
| 43 | ||
| 44 | 0 | context.setup = function(fn){ |
| 45 | 0 | suites[0].beforeEach(fn); |
| 46 | }; | |
| 47 | ||
| 48 | /** | |
| 49 | * Execute after each test case. | |
| 50 | */ | |
| 51 | ||
| 52 | 0 | context.teardown = function(fn){ |
| 53 | 0 | suites[0].afterEach(fn); |
| 54 | }; | |
| 55 | ||
| 56 | /** | |
| 57 | * Execute before the suite. | |
| 58 | */ | |
| 59 | ||
| 60 | 0 | context.suiteSetup = function(fn){ |
| 61 | 0 | suites[0].beforeAll(fn); |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Execute after the suite. | |
| 66 | */ | |
| 67 | ||
| 68 | 0 | context.suiteTeardown = function(fn){ |
| 69 | 0 | suites[0].afterAll(fn); |
| 70 | }; | |
| 71 | ||
| 72 | /** | |
| 73 | * Describe a "suite" with the given `title` | |
| 74 | * and callback `fn` containing nested suites | |
| 75 | * and/or tests. | |
| 76 | */ | |
| 77 | ||
| 78 | 0 | context.suite = function(title, fn){ |
| 79 | 0 | var suite = Suite.create(suites[0], title); |
| 80 | 0 | suites.unshift(suite); |
| 81 | 0 | fn.call(suite); |
| 82 | 0 | suites.shift(); |
| 83 | 0 | return suite; |
| 84 | }; | |
| 85 | ||
| 86 | /** | |
| 87 | * Pending suite. | |
| 88 | */ | |
| 89 | 0 | context.suite.skip = function(title, fn) { |
| 90 | 0 | var suite = Suite.create(suites[0], title); |
| 91 | 0 | suite.pending = true; |
| 92 | 0 | suites.unshift(suite); |
| 93 | 0 | fn.call(suite); |
| 94 | 0 | suites.shift(); |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Exclusive test-case. | |
| 99 | */ | |
| 100 | ||
| 101 | 0 | context.suite.only = function(title, fn){ |
| 102 | 0 | var suite = context.suite(title, fn); |
| 103 | 0 | mocha.grep(suite.fullTitle()); |
| 104 | }; | |
| 105 | ||
| 106 | /** | |
| 107 | * Describe a specification or test-case | |
| 108 | * with the given `title` and callback `fn` | |
| 109 | * acting as a thunk. | |
| 110 | */ | |
| 111 | ||
| 112 | 0 | context.test = function(title, fn){ |
| 113 | 0 | var suite = suites[0]; |
| 114 | 0 | if (suite.pending) var fn = null; |
| 115 | 0 | var test = new Test(title, fn); |
| 116 | 0 | suite.addTest(test); |
| 117 | 0 | return test; |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Exclusive test-case. | |
| 122 | */ | |
| 123 | ||
| 124 | 0 | context.test.only = function(title, fn){ |
| 125 | 0 | var test = context.test(title, fn); |
| 126 | 0 | var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$'; |
| 127 | 0 | mocha.grep(new RegExp(reString)); |
| 128 | }; | |
| 129 | ||
| 130 | /** | |
| 131 | * Pending test case. | |
| 132 | */ | |
| 133 | ||
| 134 | 0 | context.test.skip = function(title){ |
| 135 | 0 | context.test(title); |
| 136 | }; | |
| 137 | }); | |
| 138 | }; | |
| 139 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * mocha | |
| 3 | * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca> | |
| 4 | * MIT Licensed | |
| 5 | */ | |
| 6 | ||
| 7 | /** | |
| 8 | * Module dependencies. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | var path = require('path') |
| 12 | , utils = require('./utils'); | |
| 13 | ||
| 14 | /** | |
| 15 | * Expose `Mocha`. | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | exports = module.exports = Mocha; |
| 19 | ||
| 20 | /** | |
| 21 | * Expose internals. | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | exports.utils = utils; |
| 25 | 1 | exports.interfaces = require('./interfaces'); |
| 26 | 1 | exports.reporters = require('./reporters'); |
| 27 | 1 | exports.Runnable = require('./runnable'); |
| 28 | 1 | exports.Context = require('./context'); |
| 29 | 1 | exports.Runner = require('./runner'); |
| 30 | 1 | exports.Suite = require('./suite'); |
| 31 | 1 | exports.Hook = require('./hook'); |
| 32 | 1 | exports.Test = require('./test'); |
| 33 | ||
| 34 | /** | |
| 35 | * Return image `name` path. | |
| 36 | * | |
| 37 | * @param {String} name | |
| 38 | * @return {String} | |
| 39 | * @api private | |
| 40 | */ | |
| 41 | ||
| 42 | 1 | function image(name) { |
| 43 | 0 | return __dirname + '/../images/' + name + '.png'; |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Setup mocha with `options`. | |
| 48 | * | |
| 49 | * Options: | |
| 50 | * | |
| 51 | * - `ui` name "bdd", "tdd", "exports" etc | |
| 52 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` | |
| 53 | * - `globals` array of accepted globals | |
| 54 | * - `timeout` timeout in milliseconds | |
| 55 | * - `bail` bail on the first test failure | |
| 56 | * - `slow` milliseconds to wait before considering a test slow | |
| 57 | * - `ignoreLeaks` ignore global leaks | |
| 58 | * - `grep` string or regexp to filter tests with | |
| 59 | * | |
| 60 | * @param {Object} options | |
| 61 | * @api public | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | function Mocha(options) { |
| 65 | 2 | options = options || {}; |
| 66 | 2 | this.files = []; |
| 67 | 2 | this.options = options; |
| 68 | 2 | this.grep(options.grep); |
| 69 | 2 | this.suite = new exports.Suite('', new exports.Context); |
| 70 | 2 | this.ui(options.ui); |
| 71 | 2 | this.bail(options.bail); |
| 72 | 2 | this.reporter(options.reporter); |
| 73 | 3 | if (null != options.timeout) this.timeout(options.timeout); |
| 74 | 2 | this.useColors(options.useColors) |
| 75 | 2 | if (options.slow) this.slow(options.slow); |
| 76 | ||
| 77 | 2 | this.suite.on('pre-require', function (context) { |
| 78 | 4 | exports.afterEach = context.afterEach || context.teardown; |
| 79 | 4 | exports.after = context.after || context.suiteTeardown; |
| 80 | 4 | exports.beforeEach = context.beforeEach || context.setup; |
| 81 | 4 | exports.before = context.before || context.suiteSetup; |
| 82 | 4 | exports.describe = context.describe || context.suite; |
| 83 | 4 | exports.it = context.it || context.test; |
| 84 | 4 | exports.setup = context.setup || context.beforeEach; |
| 85 | 4 | exports.suiteSetup = context.suiteSetup || context.before; |
| 86 | 4 | exports.suiteTeardown = context.suiteTeardown || context.after; |
| 87 | 4 | exports.suite = context.suite || context.describe; |
| 88 | 4 | exports.teardown = context.teardown || context.afterEach; |
| 89 | 4 | exports.test = context.test || context.it; |
| 90 | }); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Enable or disable bailing on the first failure. | |
| 95 | * | |
| 96 | * @param {Boolean} [bail] | |
| 97 | * @api public | |
| 98 | */ | |
| 99 | ||
| 100 | 1 | Mocha.prototype.bail = function(bail){ |
| 101 | 2 | if (0 == arguments.length) bail = true; |
| 102 | 2 | this.suite.bail(bail); |
| 103 | 2 | return this; |
| 104 | }; | |
| 105 | ||
| 106 | /** | |
| 107 | * Add test `file`. | |
| 108 | * | |
| 109 | * @param {String} file | |
| 110 | * @api public | |
| 111 | */ | |
| 112 | ||
| 113 | 1 | Mocha.prototype.addFile = function(file){ |
| 114 | 4 | this.files.push(file); |
| 115 | 4 | return this; |
| 116 | }; | |
| 117 | ||
| 118 | /** | |
| 119 | * Set reporter to `reporter`, defaults to "dot". | |
| 120 | * | |
| 121 | * @param {String|Function} reporter name or constructor | |
| 122 | * @api public | |
| 123 | */ | |
| 124 | ||
| 125 | 1 | Mocha.prototype.reporter = function(reporter){ |
| 126 | 2 | if ('function' == typeof reporter) { |
| 127 | 0 | this._reporter = reporter; |
| 128 | } else { | |
| 129 | 2 | reporter = reporter || 'dot'; |
| 130 | 2 | var _reporter; |
| 131 | 4 | try { _reporter = require('./reporters/' + reporter); } catch (err) {}; |
| 132 | 2 | if (!_reporter) try { _reporter = require(reporter); } catch (err) {}; |
| 133 | 2 | if (!_reporter && reporter === 'teamcity') |
| 134 | 0 | console.warn('The Teamcity reporter was moved to a package named ' + |
| 135 | 'mocha-teamcity-reporter ' + | |
| 136 | '(https://npmjs.org/package/mocha-teamcity-reporter).'); | |
| 137 | 2 | if (!_reporter) throw new Error('invalid reporter "' + reporter + '"'); |
| 138 | 2 | this._reporter = _reporter; |
| 139 | } | |
| 140 | 2 | return this; |
| 141 | }; | |
| 142 | ||
| 143 | /** | |
| 144 | * Set test UI `name`, defaults to "bdd". | |
| 145 | * | |
| 146 | * @param {String} bdd | |
| 147 | * @api public | |
| 148 | */ | |
| 149 | ||
| 150 | 1 | Mocha.prototype.ui = function(name){ |
| 151 | 2 | name = name || 'bdd'; |
| 152 | 2 | this._ui = exports.interfaces[name]; |
| 153 | 2 | if (!this._ui) try { this._ui = require(name); } catch (err) {}; |
| 154 | 2 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); |
| 155 | 2 | this._ui = this._ui(this.suite); |
| 156 | 2 | return this; |
| 157 | }; | |
| 158 | ||
| 159 | /** | |
| 160 | * Load registered files. | |
| 161 | * | |
| 162 | * @api private | |
| 163 | */ | |
| 164 | ||
| 165 | 1 | Mocha.prototype.loadFiles = function(fn){ |
| 166 | 2 | var self = this; |
| 167 | 2 | var suite = this.suite; |
| 168 | 2 | var pending = this.files.length; |
| 169 | 2 | this.files.forEach(function(file){ |
| 170 | 4 | file = path.resolve(file); |
| 171 | 4 | suite.emit('pre-require', global, file, self); |
| 172 | 4 | suite.emit('require', require(file), file, self); |
| 173 | 4 | suite.emit('post-require', global, file, self); |
| 174 | 4 | --pending || (fn && fn()); |
| 175 | }); | |
| 176 | }; | |
| 177 | ||
| 178 | /** | |
| 179 | * Enable growl support. | |
| 180 | * | |
| 181 | * @api private | |
| 182 | */ | |
| 183 | ||
| 184 | 1 | Mocha.prototype._growl = function(runner, reporter) { |
| 185 | 0 | var notify = require('growl'); |
| 186 | ||
| 187 | 0 | runner.on('end', function(){ |
| 188 | 0 | var stats = reporter.stats; |
| 189 | 0 | if (stats.failures) { |
| 190 | 0 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; |
| 191 | 0 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); |
| 192 | } else { | |
| 193 | 0 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { |
| 194 | name: 'mocha' | |
| 195 | , title: 'Passed' | |
| 196 | , image: image('ok') | |
| 197 | }); | |
| 198 | } | |
| 199 | }); | |
| 200 | }; | |
| 201 | ||
| 202 | /** | |
| 203 | * Add regexp to grep, if `re` is a string it is escaped. | |
| 204 | * | |
| 205 | * @param {RegExp|String} re | |
| 206 | * @return {Mocha} | |
| 207 | * @api public | |
| 208 | */ | |
| 209 | ||
| 210 | 1 | Mocha.prototype.grep = function(re){ |
| 211 | 2 | this.options.grep = 'string' == typeof re |
| 212 | ? new RegExp(utils.escapeRegexp(re)) | |
| 213 | : re; | |
| 214 | 2 | return this; |
| 215 | }; | |
| 216 | ||
| 217 | /** | |
| 218 | * Invert `.grep()` matches. | |
| 219 | * | |
| 220 | * @return {Mocha} | |
| 221 | * @api public | |
| 222 | */ | |
| 223 | ||
| 224 | 1 | Mocha.prototype.invert = function(){ |
| 225 | 0 | this.options.invert = true; |
| 226 | 0 | return this; |
| 227 | }; | |
| 228 | ||
| 229 | /** | |
| 230 | * Ignore global leaks. | |
| 231 | * | |
| 232 | * @param {Boolean} ignore | |
| 233 | * @return {Mocha} | |
| 234 | * @api public | |
| 235 | */ | |
| 236 | ||
| 237 | 1 | Mocha.prototype.ignoreLeaks = function(ignore){ |
| 238 | 0 | this.options.ignoreLeaks = !!ignore; |
| 239 | 0 | return this; |
| 240 | }; | |
| 241 | ||
| 242 | /** | |
| 243 | * Enable global leak checking. | |
| 244 | * | |
| 245 | * @return {Mocha} | |
| 246 | * @api public | |
| 247 | */ | |
| 248 | ||
| 249 | 1 | Mocha.prototype.checkLeaks = function(){ |
| 250 | 0 | this.options.ignoreLeaks = false; |
| 251 | 0 | return this; |
| 252 | }; | |
| 253 | ||
| 254 | /** | |
| 255 | * Enable growl support. | |
| 256 | * | |
| 257 | * @return {Mocha} | |
| 258 | * @api public | |
| 259 | */ | |
| 260 | ||
| 261 | 1 | Mocha.prototype.growl = function(){ |
| 262 | 0 | this.options.growl = true; |
| 263 | 0 | return this; |
| 264 | }; | |
| 265 | ||
| 266 | /** | |
| 267 | * Ignore `globals` array or string. | |
| 268 | * | |
| 269 | * @param {Array|String} globals | |
| 270 | * @return {Mocha} | |
| 271 | * @api public | |
| 272 | */ | |
| 273 | ||
| 274 | 1 | Mocha.prototype.globals = function(globals){ |
| 275 | 0 | this.options.globals = (this.options.globals || []).concat(globals); |
| 276 | 0 | return this; |
| 277 | }; | |
| 278 | ||
| 279 | /** | |
| 280 | * Emit color output. | |
| 281 | * | |
| 282 | * @param {Boolean} colors | |
| 283 | * @return {Mocha} | |
| 284 | * @api public | |
| 285 | */ | |
| 286 | ||
| 287 | 1 | Mocha.prototype.useColors = function(colors){ |
| 288 | 2 | this.options.useColors = arguments.length && colors != undefined |
| 289 | ? colors | |
| 290 | : true; | |
| 291 | 2 | return this; |
| 292 | }; | |
| 293 | ||
| 294 | /** | |
| 295 | * Use inline diffs rather than +/-. | |
| 296 | * | |
| 297 | * @param {Boolean} inlineDiffs | |
| 298 | * @return {Mocha} | |
| 299 | * @api public | |
| 300 | */ | |
| 301 | ||
| 302 | 1 | Mocha.prototype.useInlineDiffs = function(inlineDiffs) { |
| 303 | 0 | this.options.useInlineDiffs = arguments.length && inlineDiffs != undefined |
| 304 | ? inlineDiffs | |
| 305 | : false; | |
| 306 | 0 | return this; |
| 307 | }; | |
| 308 | ||
| 309 | /** | |
| 310 | * Set the timeout in milliseconds. | |
| 311 | * | |
| 312 | * @param {Number} timeout | |
| 313 | * @return {Mocha} | |
| 314 | * @api public | |
| 315 | */ | |
| 316 | ||
| 317 | 1 | Mocha.prototype.timeout = function(timeout){ |
| 318 | 1 | this.suite.timeout(timeout); |
| 319 | 1 | return this; |
| 320 | }; | |
| 321 | ||
| 322 | /** | |
| 323 | * Set slowness threshold in milliseconds. | |
| 324 | * | |
| 325 | * @param {Number} slow | |
| 326 | * @return {Mocha} | |
| 327 | * @api public | |
| 328 | */ | |
| 329 | ||
| 330 | 1 | Mocha.prototype.slow = function(slow){ |
| 331 | 0 | this.suite.slow(slow); |
| 332 | 0 | return this; |
| 333 | }; | |
| 334 | ||
| 335 | /** | |
| 336 | * Makes all tests async (accepting a callback) | |
| 337 | * | |
| 338 | * @return {Mocha} | |
| 339 | * @api public | |
| 340 | */ | |
| 341 | ||
| 342 | 1 | Mocha.prototype.asyncOnly = function(){ |
| 343 | 0 | this.options.asyncOnly = true; |
| 344 | 0 | return this; |
| 345 | }; | |
| 346 | ||
| 347 | /** | |
| 348 | * Run tests and invoke `fn()` when complete. | |
| 349 | * | |
| 350 | * @param {Function} fn | |
| 351 | * @return {Runner} | |
| 352 | * @api public | |
| 353 | */ | |
| 354 | ||
| 355 | 1 | Mocha.prototype.run = function(fn){ |
| 356 | 0 | if (this.files.length) this.loadFiles(); |
| 357 | 0 | var suite = this.suite; |
| 358 | 0 | var options = this.options; |
| 359 | 0 | var runner = new exports.Runner(suite); |
| 360 | 0 | var reporter = new this._reporter(runner); |
| 361 | 0 | runner.ignoreLeaks = false !== options.ignoreLeaks; |
| 362 | 0 | runner.asyncOnly = options.asyncOnly; |
| 363 | 0 | if (options.grep) runner.grep(options.grep, options.invert); |
| 364 | 0 | if (options.globals) runner.globals(options.globals); |
| 365 | 0 | if (options.growl) this._growl(runner, reporter); |
| 366 | 0 | exports.reporters.Base.useColors = options.useColors; |
| 367 | 0 | exports.reporters.Base.inlineDiffs = options.useInlineDiffs; |
| 368 | 0 | return runner.run(fn); |
| 369 | }; | |
| 370 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Helpers. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var s = 1000; |
| 6 | 1 | var m = s * 60; |
| 7 | 1 | var h = m * 60; |
| 8 | 1 | var d = h * 24; |
| 9 | 1 | var y = d * 365.25; |
| 10 | ||
| 11 | /** | |
| 12 | * Parse or format the given `val`. | |
| 13 | * | |
| 14 | * Options: | |
| 15 | * | |
| 16 | * - `long` verbose formatting [false] | |
| 17 | * | |
| 18 | * @param {String|Number} val | |
| 19 | * @param {Object} options | |
| 20 | * @return {String|Number} | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | module.exports = function(val, options){ |
| 25 | 1 | options = options || {}; |
| 26 | 1 | if ('string' == typeof val) return parse(val); |
| 27 | 1 | return options.long ? longFormat(val) : shortFormat(val); |
| 28 | }; | |
| 29 | ||
| 30 | /** | |
| 31 | * Parse the given `str` and return milliseconds. | |
| 32 | * | |
| 33 | * @param {String} str | |
| 34 | * @return {Number} | |
| 35 | * @api private | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | function parse(str) { |
| 39 | 0 | var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); |
| 40 | 0 | if (!match) return; |
| 41 | 0 | var n = parseFloat(match[1]); |
| 42 | 0 | var type = (match[2] || 'ms').toLowerCase(); |
| 43 | 0 | switch (type) { |
| 44 | case 'years': | |
| 45 | case 'year': | |
| 46 | case 'y': | |
| 47 | 0 | return n * y; |
| 48 | case 'days': | |
| 49 | case 'day': | |
| 50 | case 'd': | |
| 51 | 0 | return n * d; |
| 52 | case 'hours': | |
| 53 | case 'hour': | |
| 54 | case 'h': | |
| 55 | 0 | return n * h; |
| 56 | case 'minutes': | |
| 57 | case 'minute': | |
| 58 | case 'm': | |
| 59 | 0 | return n * m; |
| 60 | case 'seconds': | |
| 61 | case 'second': | |
| 62 | case 's': | |
| 63 | 0 | return n * s; |
| 64 | case 'ms': | |
| 65 | 0 | return n; |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * Short format for `ms`. | |
| 71 | * | |
| 72 | * @param {Number} ms | |
| 73 | * @return {String} | |
| 74 | * @api private | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | function shortFormat(ms) { |
| 78 | 1 | if (ms >= d) return Math.round(ms / d) + 'd'; |
| 79 | 1 | if (ms >= h) return Math.round(ms / h) + 'h'; |
| 80 | 1 | if (ms >= m) return Math.round(ms / m) + 'm'; |
| 81 | 1 | if (ms >= s) return Math.round(ms / s) + 's'; |
| 82 | 1 | return ms + 'ms'; |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Long format for `ms`. | |
| 87 | * | |
| 88 | * @param {Number} ms | |
| 89 | * @return {String} | |
| 90 | * @api private | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function longFormat(ms) { |
| 94 | 0 | return plural(ms, d, 'day') |
| 95 | || plural(ms, h, 'hour') | |
| 96 | || plural(ms, m, 'minute') | |
| 97 | || plural(ms, s, 'second') | |
| 98 | || ms + ' ms'; | |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Pluralization helper. | |
| 103 | */ | |
| 104 | ||
| 105 | 1 | function plural(ms, n, name) { |
| 106 | 0 | if (ms < n) return; |
| 107 | 0 | if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name; |
| 108 | 0 | return Math.ceil(ms / n) + ' ' + name + 's'; |
| 109 | } | |
| 110 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var tty = require('tty') |
| 7 | , diff = require('diff') | |
| 8 | , ms = require('../ms') | |
| 9 | , utils = require('../utils'); | |
| 10 | ||
| 11 | /** | |
| 12 | * Save timer references to avoid Sinon interfering (see GH-237). | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | var Date = global.Date |
| 16 | , setTimeout = global.setTimeout | |
| 17 | , setInterval = global.setInterval | |
| 18 | , clearTimeout = global.clearTimeout | |
| 19 | , clearInterval = global.clearInterval; | |
| 20 | ||
| 21 | /** | |
| 22 | * Check if both stdio streams are associated with a tty. | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | var isatty = tty.isatty(1) && tty.isatty(2); |
| 26 | ||
| 27 | /** | |
| 28 | * Expose `Base`. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | exports = module.exports = Base; |
| 32 | ||
| 33 | /** | |
| 34 | * Enable coloring by default. | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | exports.useColors = isatty || (process.env.MOCHA_COLORS !== undefined); |
| 38 | ||
| 39 | /** | |
| 40 | * Inline diffs instead of +/- | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | exports.inlineDiffs = false; |
| 44 | ||
| 45 | /** | |
| 46 | * Default color map. | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | exports.colors = { |
| 50 | 'pass': 90 | |
| 51 | , 'fail': 31 | |
| 52 | , 'bright pass': 92 | |
| 53 | , 'bright fail': 91 | |
| 54 | , 'bright yellow': 93 | |
| 55 | , 'pending': 36 | |
| 56 | , 'suite': 0 | |
| 57 | , 'error title': 0 | |
| 58 | , 'error message': 31 | |
| 59 | , 'error stack': 90 | |
| 60 | , 'checkmark': 32 | |
| 61 | , 'fast': 90 | |
| 62 | , 'medium': 33 | |
| 63 | , 'slow': 31 | |
| 64 | , 'green': 32 | |
| 65 | , 'light': 90 | |
| 66 | , 'diff gutter': 90 | |
| 67 | , 'diff added': 42 | |
| 68 | , 'diff removed': 41 | |
| 69 | }; | |
| 70 | ||
| 71 | /** | |
| 72 | * Default symbol map. | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | exports.symbols = { |
| 76 | ok: '✓', | |
| 77 | err: '✖', | |
| 78 | dot: '․' | |
| 79 | }; | |
| 80 | ||
| 81 | // With node.js on Windows: use symbols available in terminal default fonts | |
| 82 | 1 | if ('win32' == process.platform) { |
| 83 | 0 | exports.symbols.ok = '\u221A'; |
| 84 | 0 | exports.symbols.err = '\u00D7'; |
| 85 | 0 | exports.symbols.dot = '.'; |
| 86 | } | |
| 87 | ||
| 88 | /** | |
| 89 | * Color `str` with the given `type`, | |
| 90 | * allowing colors to be disabled, | |
| 91 | * as well as user-defined color | |
| 92 | * schemes. | |
| 93 | * | |
| 94 | * @param {String} type | |
| 95 | * @param {String} str | |
| 96 | * @return {String} | |
| 97 | * @api private | |
| 98 | */ | |
| 99 | ||
| 100 | 1 | var color = exports.color = function(type, str) { |
| 101 | 12 | if (!exports.useColors) return str; |
| 102 | 12 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; |
| 103 | }; | |
| 104 | ||
| 105 | /** | |
| 106 | * Expose term window size, with some | |
| 107 | * defaults for when stderr is not a tty. | |
| 108 | */ | |
| 109 | ||
| 110 | 1 | exports.window = { |
| 111 | width: isatty | |
| 112 | ? process.stdout.getWindowSize | |
| 113 | ? process.stdout.getWindowSize(1)[0] | |
| 114 | : tty.getWindowSize()[1] | |
| 115 | : 75 | |
| 116 | }; | |
| 117 | ||
| 118 | /** | |
| 119 | * Expose some basic cursor interactions | |
| 120 | * that are common among reporters. | |
| 121 | */ | |
| 122 | ||
| 123 | 1 | exports.cursor = { |
| 124 | hide: function(){ | |
| 125 | 0 | isatty && process.stdout.write('\u001b[?25l'); |
| 126 | }, | |
| 127 | ||
| 128 | show: function(){ | |
| 129 | 0 | isatty && process.stdout.write('\u001b[?25h'); |
| 130 | }, | |
| 131 | ||
| 132 | deleteLine: function(){ | |
| 133 | 1 | isatty && process.stdout.write('\u001b[2K'); |
| 134 | }, | |
| 135 | ||
| 136 | beginningOfLine: function(){ | |
| 137 | 1 | isatty && process.stdout.write('\u001b[0G'); |
| 138 | }, | |
| 139 | ||
| 140 | CR: function(){ | |
| 141 | 1 | if (isatty) { |
| 142 | 1 | exports.cursor.deleteLine(); |
| 143 | 1 | exports.cursor.beginningOfLine(); |
| 144 | } else { | |
| 145 | 0 | process.stdout.write('\r'); |
| 146 | } | |
| 147 | } | |
| 148 | }; | |
| 149 | ||
| 150 | /** | |
| 151 | * Outut the given `failures` as a list. | |
| 152 | * | |
| 153 | * @param {Array} failures | |
| 154 | * @api public | |
| 155 | */ | |
| 156 | ||
| 157 | 1 | exports.list = function(failures){ |
| 158 | 0 | console.error(); |
| 159 | 0 | failures.forEach(function(test, i){ |
| 160 | // format | |
| 161 | 0 | var fmt = color('error title', ' %s) %s:\n') |
| 162 | + color('error message', ' %s') | |
| 163 | + color('error stack', '\n%s\n'); | |
| 164 | ||
| 165 | // msg | |
| 166 | 0 | var err = test.err |
| 167 | , message = err.message || '' | |
| 168 | , stack = err.stack || message | |
| 169 | , index = stack.indexOf(message) + message.length | |
| 170 | , msg = stack.slice(0, index) | |
| 171 | , actual = err.actual | |
| 172 | , expected = err.expected | |
| 173 | , escape = true; | |
| 174 | ||
| 175 | // uncaught | |
| 176 | 0 | if (err.uncaught) { |
| 177 | 0 | msg = 'Uncaught ' + msg; |
| 178 | } | |
| 179 | ||
| 180 | // explicitly show diff | |
| 181 | 0 | if (err.showDiff && sameType(actual, expected)) { |
| 182 | 0 | escape = false; |
| 183 | 0 | err.actual = actual = stringify(canonicalize(actual)); |
| 184 | 0 | err.expected = expected = stringify(canonicalize(expected)); |
| 185 | } | |
| 186 | ||
| 187 | // actual / expected diff | |
| 188 | 0 | if ('string' == typeof actual && 'string' == typeof expected) { |
| 189 | 0 | fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); |
| 190 | 0 | var match = message.match(/^([^:]+): expected/); |
| 191 | 0 | msg = '\n ' + color('error message', match ? match[1] : msg); |
| 192 | ||
| 193 | 0 | if (exports.inlineDiffs) { |
| 194 | 0 | msg += inlineDiff(err, escape); |
| 195 | } else { | |
| 196 | 0 | msg += unifiedDiff(err, escape); |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | // indent stack trace without msg | |
| 201 | 0 | stack = stack.slice(index ? index + 1 : index) |
| 202 | .replace(/^/gm, ' '); | |
| 203 | ||
| 204 | 0 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); |
| 205 | }); | |
| 206 | }; | |
| 207 | ||
| 208 | /** | |
| 209 | * Initialize a new `Base` reporter. | |
| 210 | * | |
| 211 | * All other reporters generally | |
| 212 | * inherit from this reporter, providing | |
| 213 | * stats such as test duration, number | |
| 214 | * of tests passed / failed etc. | |
| 215 | * | |
| 216 | * @param {Runner} runner | |
| 217 | * @api public | |
| 218 | */ | |
| 219 | ||
| 220 | 1 | function Base(runner) { |
| 221 | 2 | var self = this |
| 222 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } | |
| 223 | , failures = this.failures = []; | |
| 224 | ||
| 225 | 2 | if (!runner) return; |
| 226 | 2 | this.runner = runner; |
| 227 | ||
| 228 | 2 | runner.stats = stats; |
| 229 | ||
| 230 | 2 | runner.on('start', function(){ |
| 231 | 2 | stats.start = new Date; |
| 232 | }); | |
| 233 | ||
| 234 | 2 | runner.on('suite', function(suite){ |
| 235 | 4 | stats.suites = stats.suites || 0; |
| 236 | 4 | suite.root || stats.suites++; |
| 237 | }); | |
| 238 | ||
| 239 | 2 | runner.on('test end', function(test){ |
| 240 | 2 | stats.tests = stats.tests || 0; |
| 241 | 2 | stats.tests++; |
| 242 | }); | |
| 243 | ||
| 244 | 2 | runner.on('pass', function(test){ |
| 245 | 1 | stats.passes = stats.passes || 0; |
| 246 | ||
| 247 | 1 | var medium = test.slow() / 2; |
| 248 | 1 | test.speed = test.duration > test.slow() |
| 249 | ? 'slow' | |
| 250 | : test.duration > medium | |
| 251 | ? 'medium' | |
| 252 | : 'fast'; | |
| 253 | ||
| 254 | 1 | stats.passes++; |
| 255 | }); | |
| 256 | ||
| 257 | 2 | runner.on('fail', function(test, err){ |
| 258 | 0 | stats.failures = stats.failures || 0; |
| 259 | 0 | stats.failures++; |
| 260 | 0 | test.err = err; |
| 261 | 0 | failures.push(test); |
| 262 | }); | |
| 263 | ||
| 264 | 2 | runner.on('end', function(){ |
| 265 | 2 | stats.end = new Date; |
| 266 | 2 | stats.duration = new Date - stats.start; |
| 267 | }); | |
| 268 | ||
| 269 | 2 | runner.on('pending', function(){ |
| 270 | 1 | stats.pending++; |
| 271 | }); | |
| 272 | } | |
| 273 | ||
| 274 | /** | |
| 275 | * Output common epilogue used by many of | |
| 276 | * the bundled reporters. | |
| 277 | * | |
| 278 | * @api public | |
| 279 | */ | |
| 280 | ||
| 281 | 1 | Base.prototype.epilogue = function(){ |
| 282 | 1 | var stats = this.stats; |
| 283 | 1 | var tests; |
| 284 | 1 | var fmt; |
| 285 | ||
| 286 | 1 | console.log(); |
| 287 | ||
| 288 | // passes | |
| 289 | 1 | fmt = color('bright pass', ' ') |
| 290 | + color('green', ' %d passing') | |
| 291 | + color('light', ' (%s)'); | |
| 292 | ||
| 293 | 1 | console.log(fmt, |
| 294 | stats.passes || 0, | |
| 295 | ms(stats.duration)); | |
| 296 | ||
| 297 | // pending | |
| 298 | 1 | if (stats.pending) { |
| 299 | 1 | fmt = color('pending', ' ') |
| 300 | + color('pending', ' %d pending'); | |
| 301 | ||
| 302 | 1 | console.log(fmt, stats.pending); |
| 303 | } | |
| 304 | ||
| 305 | // failures | |
| 306 | 1 | if (stats.failures) { |
| 307 | 0 | fmt = color('fail', ' %d failing'); |
| 308 | ||
| 309 | 0 | console.error(fmt, |
| 310 | stats.failures); | |
| 311 | ||
| 312 | 0 | Base.list(this.failures); |
| 313 | 0 | console.error(); |
| 314 | } | |
| 315 | ||
| 316 | 1 | console.log(); |
| 317 | }; | |
| 318 | ||
| 319 | /** | |
| 320 | * Pad the given `str` to `len`. | |
| 321 | * | |
| 322 | * @param {String} str | |
| 323 | * @param {String} len | |
| 324 | * @return {String} | |
| 325 | * @api private | |
| 326 | */ | |
| 327 | ||
| 328 | 1 | function pad(str, len) { |
| 329 | 0 | str = String(str); |
| 330 | 0 | return Array(len - str.length + 1).join(' ') + str; |
| 331 | } | |
| 332 | ||
| 333 | ||
| 334 | /** | |
| 335 | * Returns an inline diff between 2 strings with coloured ANSI output | |
| 336 | * | |
| 337 | * @param {Error} Error with actual/expected | |
| 338 | * @return {String} Diff | |
| 339 | * @api private | |
| 340 | */ | |
| 341 | ||
| 342 | 1 | function inlineDiff(err, escape) { |
| 343 | 0 | var msg = errorDiff(err, 'WordsWithSpace', escape); |
| 344 | ||
| 345 | // linenos | |
| 346 | 0 | var lines = msg.split('\n'); |
| 347 | 0 | if (lines.length > 4) { |
| 348 | 0 | var width = String(lines.length).length; |
| 349 | 0 | msg = lines.map(function(str, i){ |
| 350 | 0 | return pad(++i, width) + ' |' + ' ' + str; |
| 351 | }).join('\n'); | |
| 352 | } | |
| 353 | ||
| 354 | // legend | |
| 355 | 0 | msg = '\n' |
| 356 | + color('diff removed', 'actual') | |
| 357 | + ' ' | |
| 358 | + color('diff added', 'expected') | |
| 359 | + '\n\n' | |
| 360 | + msg | |
| 361 | + '\n'; | |
| 362 | ||
| 363 | // indent | |
| 364 | 0 | msg = msg.replace(/^/gm, ' '); |
| 365 | 0 | return msg; |
| 366 | } | |
| 367 | ||
| 368 | /** | |
| 369 | * Returns a unified diff between 2 strings | |
| 370 | * | |
| 371 | * @param {Error} Error with actual/expected | |
| 372 | * @return {String} Diff | |
| 373 | * @api private | |
| 374 | */ | |
| 375 | ||
| 376 | 1 | function unifiedDiff(err, escape) { |
| 377 | 0 | var indent = ' '; |
| 378 | 0 | function cleanUp(line) { |
| 379 | 0 | if (escape) { |
| 380 | 0 | line = escapeInvisibles(line); |
| 381 | } | |
| 382 | 0 | if (line[0] === '+') return indent + colorLines('diff added', line); |
| 383 | 0 | if (line[0] === '-') return indent + colorLines('diff removed', line); |
| 384 | 0 | if (line.match(/\@\@/)) return null; |
| 385 | 0 | if (line.match(/\\ No newline/)) return null; |
| 386 | 0 | else return indent + line; |
| 387 | } | |
| 388 | 0 | function notBlank(line) { |
| 389 | 0 | return line != null; |
| 390 | } | |
| 391 | 0 | msg = diff.createPatch('string', err.actual, err.expected); |
| 392 | 0 | var lines = msg.split('\n').splice(4); |
| 393 | 0 | return '\n ' |
| 394 | + colorLines('diff added', '+ expected') + ' ' | |
| 395 | + colorLines('diff removed', '- actual') | |
| 396 | + '\n\n' | |
| 397 | + lines.map(cleanUp).filter(notBlank).join('\n'); | |
| 398 | } | |
| 399 | ||
| 400 | /** | |
| 401 | * Return a character diff for `err`. | |
| 402 | * | |
| 403 | * @param {Error} err | |
| 404 | * @return {String} | |
| 405 | * @api private | |
| 406 | */ | |
| 407 | ||
| 408 | 1 | function errorDiff(err, type, escape) { |
| 409 | 0 | var actual = escape ? escapeInvisibles(err.actual) : err.actual; |
| 410 | 0 | var expected = escape ? escapeInvisibles(err.expected) : err.expected; |
| 411 | 0 | return diff['diff' + type](actual, expected).map(function(str){ |
| 412 | 0 | if (str.added) return colorLines('diff added', str.value); |
| 413 | 0 | if (str.removed) return colorLines('diff removed', str.value); |
| 414 | 0 | return str.value; |
| 415 | }).join(''); | |
| 416 | } | |
| 417 | ||
| 418 | /** | |
| 419 | * Returns a string with all invisible characters in plain text | |
| 420 | * | |
| 421 | * @param {String} line | |
| 422 | * @return {String} | |
| 423 | * @api private | |
| 424 | */ | |
| 425 | 1 | function escapeInvisibles(line) { |
| 426 | 0 | return line.replace(/\t/g, '<tab>') |
| 427 | .replace(/\r/g, '<CR>') | |
| 428 | .replace(/\n/g, '<LF>\n'); | |
| 429 | } | |
| 430 | ||
| 431 | /** | |
| 432 | * Color lines for `str`, using the color `name`. | |
| 433 | * | |
| 434 | * @param {String} name | |
| 435 | * @param {String} str | |
| 436 | * @return {String} | |
| 437 | * @api private | |
| 438 | */ | |
| 439 | ||
| 440 | 1 | function colorLines(name, str) { |
| 441 | 0 | return str.split('\n').map(function(str){ |
| 442 | 0 | return color(name, str); |
| 443 | }).join('\n'); | |
| 444 | } | |
| 445 | ||
| 446 | /** | |
| 447 | * Stringify `obj`. | |
| 448 | * | |
| 449 | * @param {Object} obj | |
| 450 | * @return {String} | |
| 451 | * @api private | |
| 452 | */ | |
| 453 | ||
| 454 | 1 | function stringify(obj) { |
| 455 | 0 | if (obj instanceof RegExp) return obj.toString(); |
| 456 | 0 | return JSON.stringify(obj, null, 2); |
| 457 | } | |
| 458 | ||
| 459 | /** | |
| 460 | * Return a new object that has the keys in sorted order. | |
| 461 | * @param {Object} obj | |
| 462 | * @return {Object} | |
| 463 | * @api private | |
| 464 | */ | |
| 465 | ||
| 466 | 1 | function canonicalize(obj, stack) { |
| 467 | 0 | stack = stack || []; |
| 468 | ||
| 469 | 0 | if (utils.indexOf(stack, obj) !== -1) return obj; |
| 470 | ||
| 471 | 0 | var canonicalizedObj; |
| 472 | ||
| 473 | 0 | if ('[object Array]' == {}.toString.call(obj)) { |
| 474 | 0 | stack.push(obj); |
| 475 | 0 | canonicalizedObj = utils.map(obj, function(item) { |
| 476 | 0 | return canonicalize(item, stack); |
| 477 | }); | |
| 478 | 0 | stack.pop(); |
| 479 | 0 | } else if (typeof obj === 'object' && obj !== null) { |
| 480 | 0 | stack.push(obj); |
| 481 | 0 | canonicalizedObj = {}; |
| 482 | 0 | utils.forEach(utils.keys(obj).sort(), function(key) { |
| 483 | 0 | canonicalizedObj[key] = canonicalize(obj[key], stack); |
| 484 | }); | |
| 485 | 0 | stack.pop(); |
| 486 | } else { | |
| 487 | 0 | canonicalizedObj = obj; |
| 488 | } | |
| 489 | ||
| 490 | 0 | return canonicalizedObj; |
| 491 | } | |
| 492 | ||
| 493 | /** | |
| 494 | * Check that a / b have the same type. | |
| 495 | * | |
| 496 | * @param {Object} a | |
| 497 | * @param {Object} b | |
| 498 | * @return {Boolean} | |
| 499 | * @api private | |
| 500 | */ | |
| 501 | ||
| 502 | 1 | function sameType(a, b) { |
| 503 | 0 | a = Object.prototype.toString.call(a); |
| 504 | 0 | b = Object.prototype.toString.call(b); |
| 505 | 0 | return a == b; |
| 506 | } | |
| 507 | ||
| 508 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , utils = require('../utils'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Expose `Doc`. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | exports = module.exports = Doc; |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a new `Doc` reporter. | |
| 17 | * | |
| 18 | * @param {Runner} runner | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function Doc(runner) { |
| 23 | 0 | Base.call(this, runner); |
| 24 | ||
| 25 | 0 | var self = this |
| 26 | , stats = this.stats | |
| 27 | , total = runner.total | |
| 28 | , indents = 2; | |
| 29 | ||
| 30 | 0 | function indent() { |
| 31 | 0 | return Array(indents).join(' '); |
| 32 | } | |
| 33 | ||
| 34 | 0 | runner.on('suite', function(suite){ |
| 35 | 0 | if (suite.root) return; |
| 36 | 0 | ++indents; |
| 37 | 0 | console.log('%s<section class="suite">', indent()); |
| 38 | 0 | ++indents; |
| 39 | 0 | console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title)); |
| 40 | 0 | console.log('%s<dl>', indent()); |
| 41 | }); | |
| 42 | ||
| 43 | 0 | runner.on('suite end', function(suite){ |
| 44 | 0 | if (suite.root) return; |
| 45 | 0 | console.log('%s</dl>', indent()); |
| 46 | 0 | --indents; |
| 47 | 0 | console.log('%s</section>', indent()); |
| 48 | 0 | --indents; |
| 49 | }); | |
| 50 | ||
| 51 | 0 | runner.on('pass', function(test){ |
| 52 | 0 | console.log('%s <dt>%s</dt>', indent(), utils.escape(test.title)); |
| 53 | 0 | var code = utils.escape(utils.clean(test.fn.toString())); |
| 54 | 0 | console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code); |
| 55 | }); | |
| 56 | } | |
| 57 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , color = Base.color; | |
| 8 | ||
| 9 | /** | |
| 10 | * Expose `Dot`. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | exports = module.exports = Dot; |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a new `Dot` matrix test reporter. | |
| 17 | * | |
| 18 | * @param {Runner} runner | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function Dot(runner) { |
| 23 | 0 | Base.call(this, runner); |
| 24 | ||
| 25 | 0 | var self = this |
| 26 | , stats = this.stats | |
| 27 | , width = Base.window.width * .75 | 0 | |
| 28 | , n = 0; | |
| 29 | ||
| 30 | 0 | runner.on('start', function(){ |
| 31 | 0 | process.stdout.write('\n '); |
| 32 | }); | |
| 33 | ||
| 34 | 0 | runner.on('pending', function(test){ |
| 35 | 0 | process.stdout.write(color('pending', Base.symbols.dot)); |
| 36 | }); | |
| 37 | ||
| 38 | 0 | runner.on('pass', function(test){ |
| 39 | 0 | if (++n % width == 0) process.stdout.write('\n '); |
| 40 | 0 | if ('slow' == test.speed) { |
| 41 | 0 | process.stdout.write(color('bright yellow', Base.symbols.dot)); |
| 42 | } else { | |
| 43 | 0 | process.stdout.write(color(test.speed, Base.symbols.dot)); |
| 44 | } | |
| 45 | }); | |
| 46 | ||
| 47 | 0 | runner.on('fail', function(test, err){ |
| 48 | 0 | if (++n % width == 0) process.stdout.write('\n '); |
| 49 | 0 | process.stdout.write(color('fail', Base.symbols.dot)); |
| 50 | }); | |
| 51 | ||
| 52 | 0 | runner.on('end', function(){ |
| 53 | 0 | console.log(); |
| 54 | 0 | self.epilogue(); |
| 55 | }); | |
| 56 | } | |
| 57 | ||
| 58 | /** | |
| 59 | * Inherit from `Base.prototype`. | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | Dot.prototype.__proto__ = Base.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var JSONCov = require('./json-cov') |
| 7 | , fs = require('fs'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Expose `HTMLCov`. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | exports = module.exports = HTMLCov; |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a new `JsCoverage` reporter. | |
| 17 | * | |
| 18 | * @param {Runner} runner | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function HTMLCov(runner) { |
| 23 | 1 | var jade = require('jade') |
| 24 | , file = __dirname + '/templates/coverage.jade' | |
| 25 | , str = fs.readFileSync(file, 'utf8') | |
| 26 | , fn = jade.compile(str, { filename: file }) | |
| 27 | , self = this; | |
| 28 | ||
| 29 | 1 | JSONCov.call(this, runner, false); |
| 30 | ||
| 31 | 1 | runner.on('end', function(){ |
| 32 | 0 | process.stdout.write(fn({ |
| 33 | cov: self.cov | |
| 34 | , coverageClass: coverageClass | |
| 35 | })); | |
| 36 | }); | |
| 37 | } | |
| 38 | ||
| 39 | /** | |
| 40 | * Return coverage class for `n`. | |
| 41 | * | |
| 42 | * @return {String} | |
| 43 | * @api private | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | function coverageClass(n) { |
| 47 | 0 | if (n >= 75) return 'high'; |
| 48 | 0 | if (n >= 50) return 'medium'; |
| 49 | 0 | if (n >= 25) return 'low'; |
| 50 | 0 | return 'terrible'; |
| 51 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , utils = require('../utils') | |
| 8 | , Progress = require('../browser/progress') | |
| 9 | , escape = utils.escape; | |
| 10 | ||
| 11 | /** | |
| 12 | * Save timer references to avoid Sinon interfering (see GH-237). | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | var Date = global.Date |
| 16 | , setTimeout = global.setTimeout | |
| 17 | , setInterval = global.setInterval | |
| 18 | , clearTimeout = global.clearTimeout | |
| 19 | , clearInterval = global.clearInterval; | |
| 20 | ||
| 21 | /** | |
| 22 | * Expose `HTML`. | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | exports = module.exports = HTML; |
| 26 | ||
| 27 | /** | |
| 28 | * Stats template. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | var statsTemplate = '<ul id="mocha-stats">' |
| 32 | + '<li class="progress"><canvas width="40" height="40"></canvas></li>' | |
| 33 | + '<li class="passes"><a href="#">passes:</a> <em>0</em></li>' | |
| 34 | + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>' | |
| 35 | + '<li class="duration">duration: <em>0</em>s</li>' | |
| 36 | + '</ul>'; | |
| 37 | ||
| 38 | /** | |
| 39 | * Initialize a new `HTML` reporter. | |
| 40 | * | |
| 41 | * @param {Runner} runner | |
| 42 | * @api public | |
| 43 | */ | |
| 44 | ||
| 45 | 1 | function HTML(runner, root) { |
| 46 | 0 | Base.call(this, runner); |
| 47 | ||
| 48 | 0 | var self = this |
| 49 | , stats = this.stats | |
| 50 | , total = runner.total | |
| 51 | , stat = fragment(statsTemplate) | |
| 52 | , items = stat.getElementsByTagName('li') | |
| 53 | , passes = items[1].getElementsByTagName('em')[0] | |
| 54 | , passesLink = items[1].getElementsByTagName('a')[0] | |
| 55 | , failures = items[2].getElementsByTagName('em')[0] | |
| 56 | , failuresLink = items[2].getElementsByTagName('a')[0] | |
| 57 | , duration = items[3].getElementsByTagName('em')[0] | |
| 58 | , canvas = stat.getElementsByTagName('canvas')[0] | |
| 59 | , report = fragment('<ul id="mocha-report"></ul>') | |
| 60 | , stack = [report] | |
| 61 | , progress | |
| 62 | , ctx | |
| 63 | ||
| 64 | 0 | root = root || document.getElementById('mocha'); |
| 65 | ||
| 66 | 0 | if (canvas.getContext) { |
| 67 | 0 | var ratio = window.devicePixelRatio || 1; |
| 68 | 0 | canvas.style.width = canvas.width; |
| 69 | 0 | canvas.style.height = canvas.height; |
| 70 | 0 | canvas.width *= ratio; |
| 71 | 0 | canvas.height *= ratio; |
| 72 | 0 | ctx = canvas.getContext('2d'); |
| 73 | 0 | ctx.scale(ratio, ratio); |
| 74 | 0 | progress = new Progress; |
| 75 | } | |
| 76 | ||
| 77 | 0 | if (!root) return error('#mocha div missing, add it to your document'); |
| 78 | ||
| 79 | // pass toggle | |
| 80 | 0 | on(passesLink, 'click', function(){ |
| 81 | 0 | unhide(); |
| 82 | 0 | var name = /pass/.test(report.className) ? '' : ' pass'; |
| 83 | 0 | report.className = report.className.replace(/fail|pass/g, '') + name; |
| 84 | 0 | if (report.className.trim()) hideSuitesWithout('test pass'); |
| 85 | }); | |
| 86 | ||
| 87 | // failure toggle | |
| 88 | 0 | on(failuresLink, 'click', function(){ |
| 89 | 0 | unhide(); |
| 90 | 0 | var name = /fail/.test(report.className) ? '' : ' fail'; |
| 91 | 0 | report.className = report.className.replace(/fail|pass/g, '') + name; |
| 92 | 0 | if (report.className.trim()) hideSuitesWithout('test fail'); |
| 93 | }); | |
| 94 | ||
| 95 | 0 | root.appendChild(stat); |
| 96 | 0 | root.appendChild(report); |
| 97 | ||
| 98 | 0 | if (progress) progress.size(40); |
| 99 | ||
| 100 | 0 | runner.on('suite', function(suite){ |
| 101 | 0 | if (suite.root) return; |
| 102 | ||
| 103 | // suite | |
| 104 | 0 | var url = self.suiteURL(suite); |
| 105 | 0 | var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title)); |
| 106 | ||
| 107 | // container | |
| 108 | 0 | stack[0].appendChild(el); |
| 109 | 0 | stack.unshift(document.createElement('ul')); |
| 110 | 0 | el.appendChild(stack[0]); |
| 111 | }); | |
| 112 | ||
| 113 | 0 | runner.on('suite end', function(suite){ |
| 114 | 0 | if (suite.root) return; |
| 115 | 0 | stack.shift(); |
| 116 | }); | |
| 117 | ||
| 118 | 0 | runner.on('fail', function(test, err){ |
| 119 | 0 | if ('hook' == test.type) runner.emit('test end', test); |
| 120 | }); | |
| 121 | ||
| 122 | 0 | runner.on('test end', function(test){ |
| 123 | // TODO: add to stats | |
| 124 | 0 | var percent = stats.tests / this.total * 100 | 0; |
| 125 | 0 | if (progress) progress.update(percent).draw(ctx); |
| 126 | ||
| 127 | // update stats | |
| 128 | 0 | var ms = new Date - stats.start; |
| 129 | 0 | text(passes, stats.passes); |
| 130 | 0 | text(failures, stats.failures); |
| 131 | 0 | text(duration, (ms / 1000).toFixed(2)); |
| 132 | ||
| 133 | // test | |
| 134 | 0 | if ('passed' == test.state) { |
| 135 | 0 | var url = self.testURL(test); |
| 136 | 0 | var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url); |
| 137 | 0 | } else if (test.pending) { |
| 138 | 0 | var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title); |
| 139 | } else { | |
| 140 | 0 | var el = fragment('<li class="test fail"><h2>%e <a href="?grep=%e" class="replay">‣</a></h2></li>', test.title, encodeURIComponent(test.fullTitle())); |
| 141 | 0 | var str = test.err.stack || test.err.toString(); |
| 142 | ||
| 143 | // FF / Opera do not add the message | |
| 144 | 0 | if (!~str.indexOf(test.err.message)) { |
| 145 | 0 | str = test.err.message + '\n' + str; |
| 146 | } | |
| 147 | ||
| 148 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we | |
| 149 | // check for the result of the stringifying. | |
| 150 | 0 | if ('[object Error]' == str) str = test.err.message; |
| 151 | ||
| 152 | // Safari doesn't give you a stack. Let's at least provide a source line. | |
| 153 | 0 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { |
| 154 | 0 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; |
| 155 | } | |
| 156 | ||
| 157 | 0 | el.appendChild(fragment('<pre class="error">%e</pre>', str)); |
| 158 | } | |
| 159 | ||
| 160 | // toggle code | |
| 161 | // TODO: defer | |
| 162 | 0 | if (!test.pending) { |
| 163 | 0 | var h2 = el.getElementsByTagName('h2')[0]; |
| 164 | ||
| 165 | 0 | on(h2, 'click', function(){ |
| 166 | 0 | pre.style.display = 'none' == pre.style.display |
| 167 | ? 'block' | |
| 168 | : 'none'; | |
| 169 | }); | |
| 170 | ||
| 171 | 0 | var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString())); |
| 172 | 0 | el.appendChild(pre); |
| 173 | 0 | pre.style.display = 'none'; |
| 174 | } | |
| 175 | ||
| 176 | // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. | |
| 177 | 0 | if (stack[0]) stack[0].appendChild(el); |
| 178 | }); | |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Provide suite URL | |
| 183 | * | |
| 184 | * @param {Object} [suite] | |
| 185 | */ | |
| 186 | ||
| 187 | 1 | HTML.prototype.suiteURL = function(suite){ |
| 188 | 0 | return '?grep=' + encodeURIComponent(suite.fullTitle()); |
| 189 | }; | |
| 190 | ||
| 191 | /** | |
| 192 | * Provide test URL | |
| 193 | * | |
| 194 | * @param {Object} [test] | |
| 195 | */ | |
| 196 | ||
| 197 | 1 | HTML.prototype.testURL = function(test){ |
| 198 | 0 | return '?grep=' + encodeURIComponent(test.fullTitle()); |
| 199 | }; | |
| 200 | ||
| 201 | /** | |
| 202 | * Display error `msg`. | |
| 203 | */ | |
| 204 | ||
| 205 | 1 | function error(msg) { |
| 206 | 0 | document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg)); |
| 207 | } | |
| 208 | ||
| 209 | /** | |
| 210 | * Return a DOM fragment from `html`. | |
| 211 | */ | |
| 212 | ||
| 213 | 1 | function fragment(html) { |
| 214 | 0 | var args = arguments |
| 215 | , div = document.createElement('div') | |
| 216 | , i = 1; | |
| 217 | ||
| 218 | 0 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ |
| 219 | 0 | switch (type) { |
| 220 | 0 | case 's': return String(args[i++]); |
| 221 | 0 | case 'e': return escape(args[i++]); |
| 222 | } | |
| 223 | }); | |
| 224 | ||
| 225 | 0 | return div.firstChild; |
| 226 | } | |
| 227 | ||
| 228 | /** | |
| 229 | * Check for suites that do not have elements | |
| 230 | * with `classname`, and hide them. | |
| 231 | */ | |
| 232 | ||
| 233 | 1 | function hideSuitesWithout(classname) { |
| 234 | 0 | var suites = document.getElementsByClassName('suite'); |
| 235 | 0 | for (var i = 0; i < suites.length; i++) { |
| 236 | 0 | var els = suites[i].getElementsByClassName(classname); |
| 237 | 0 | if (0 == els.length) suites[i].className += ' hidden'; |
| 238 | } | |
| 239 | } | |
| 240 | ||
| 241 | /** | |
| 242 | * Unhide .hidden suites. | |
| 243 | */ | |
| 244 | ||
| 245 | 1 | function unhide() { |
| 246 | 0 | var els = document.getElementsByClassName('suite hidden'); |
| 247 | 0 | for (var i = 0; i < els.length; ++i) { |
| 248 | 0 | els[i].className = els[i].className.replace('suite hidden', 'suite'); |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | /** | |
| 253 | * Set `el` text to `str`. | |
| 254 | */ | |
| 255 | ||
| 256 | 1 | function text(el, str) { |
| 257 | 0 | if (el.textContent) { |
| 258 | 0 | el.textContent = str; |
| 259 | } else { | |
| 260 | 0 | el.innerText = str; |
| 261 | } | |
| 262 | } | |
| 263 | ||
| 264 | /** | |
| 265 | * Listen on `event` with callback `fn`. | |
| 266 | */ | |
| 267 | ||
| 268 | 1 | function on(el, event, fn) { |
| 269 | 0 | if (el.addEventListener) { |
| 270 | 0 | el.addEventListener(event, fn, false); |
| 271 | } else { | |
| 272 | 0 | el.attachEvent('on' + event, fn); |
| 273 | } | |
| 274 | } | |
| 275 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | 1 | exports.Base = require('./base'); |
| 3 | 1 | exports.Dot = require('./dot'); |
| 4 | 1 | exports.Doc = require('./doc'); |
| 5 | 1 | exports.TAP = require('./tap'); |
| 6 | 1 | exports.JSON = require('./json'); |
| 7 | 1 | exports.HTML = require('./html'); |
| 8 | 1 | exports.List = require('./list'); |
| 9 | 1 | exports.Min = require('./min'); |
| 10 | 1 | exports.Spec = require('./spec'); |
| 11 | 1 | exports.Nyan = require('./nyan'); |
| 12 | 1 | exports.XUnit = require('./xunit'); |
| 13 | 1 | exports.Markdown = require('./markdown'); |
| 14 | 1 | exports.Progress = require('./progress'); |
| 15 | 1 | exports.Landing = require('./landing'); |
| 16 | 1 | exports.JSONCov = require('./json-cov'); |
| 17 | 1 | exports.HTMLCov = require('./html-cov'); |
| 18 | 1 | exports.JSONStream = require('./json-stream'); |
| 19 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base'); |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `JSONCov`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | exports = module.exports = JSONCov; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `JsCoverage` reporter. | |
| 16 | * | |
| 17 | * @param {Runner} runner | |
| 18 | * @param {Boolean} output | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function JSONCov(runner, output) { |
| 23 | 1 | var self = this |
| 24 | , output = 1 == arguments.length ? true : output; | |
| 25 | ||
| 26 | 1 | Base.call(this, runner); |
| 27 | ||
| 28 | 1 | var tests = [] |
| 29 | , failures = [] | |
| 30 | , passes = []; | |
| 31 | ||
| 32 | 1 | runner.on('test end', function(test){ |
| 33 | 0 | tests.push(test); |
| 34 | }); | |
| 35 | ||
| 36 | 1 | runner.on('pass', function(test){ |
| 37 | 0 | passes.push(test); |
| 38 | }); | |
| 39 | ||
| 40 | 1 | runner.on('fail', function(test){ |
| 41 | 0 | failures.push(test); |
| 42 | }); | |
| 43 | ||
| 44 | 1 | runner.on('end', function(){ |
| 45 | 1 | var cov = global._$jscoverage || {}; |
| 46 | 1 | var result = self.cov = map(cov); |
| 47 | 0 | result.stats = self.stats; |
| 48 | 0 | result.tests = tests.map(clean); |
| 49 | 0 | result.failures = failures.map(clean); |
| 50 | 0 | result.passes = passes.map(clean); |
| 51 | 0 | if (!output) return; |
| 52 | 0 | process.stdout.write(JSON.stringify(result, null, 2 )); |
| 53 | }); | |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * Map jscoverage data to a JSON structure | |
| 58 | * suitable for reporting. | |
| 59 | * | |
| 60 | * @param {Object} cov | |
| 61 | * @return {Object} | |
| 62 | * @api private | |
| 63 | */ | |
| 64 | ||
| 65 | 1 | function map(cov) { |
| 66 | 1 | var ret = { |
| 67 | instrumentation: 'node-jscoverage' | |
| 68 | , sloc: 0 | |
| 69 | , hits: 0 | |
| 70 | , misses: 0 | |
| 71 | , coverage: 0 | |
| 72 | , files: [] | |
| 73 | }; | |
| 74 | ||
| 75 | 1 | for (var filename in cov) { |
| 76 | 30 | var data = coverage(filename, cov[filename]); |
| 77 | 29 | ret.files.push(data); |
| 78 | 29 | ret.hits += data.hits; |
| 79 | 29 | ret.misses += data.misses; |
| 80 | 29 | ret.sloc += data.sloc; |
| 81 | } | |
| 82 | ||
| 83 | 0 | ret.files.sort(function(a, b) { |
| 84 | 0 | return a.filename.localeCompare(b.filename); |
| 85 | }); | |
| 86 | ||
| 87 | 0 | if (ret.sloc > 0) { |
| 88 | 0 | ret.coverage = (ret.hits / ret.sloc) * 100; |
| 89 | } | |
| 90 | ||
| 91 | 0 | return ret; |
| 92 | }; | |
| 93 | ||
| 94 | /** | |
| 95 | * Map jscoverage data for a single source file | |
| 96 | * to a JSON structure suitable for reporting. | |
| 97 | * | |
| 98 | * @param {String} filename name of the source file | |
| 99 | * @param {Object} data jscoverage coverage data | |
| 100 | * @return {Object} | |
| 101 | * @api private | |
| 102 | */ | |
| 103 | ||
| 104 | 1 | function coverage(filename, data) { |
| 105 | 30 | var ret = { |
| 106 | filename: filename, | |
| 107 | coverage: 0, | |
| 108 | hits: 0, | |
| 109 | misses: 0, | |
| 110 | sloc: 0, | |
| 111 | source: {} | |
| 112 | }; | |
| 113 | ||
| 114 | 30 | data.source.forEach(function(line, num){ |
| 115 | 4153 | num++; |
| 116 | ||
| 117 | 4155 | if (data[num] === 0) { |
| 118 | 796 | ret.misses++; |
| 119 | 796 | ret.sloc++; |
| 120 | 3362 | } else if (data[num] !== undefined) { |
| 121 | 587 | ret.hits++; |
| 122 | 588 | ret.sloc++; |
| 123 | } | |
| 124 | ||
| 125 | 4163 | ret.source[num] = { |
| 126 | source: line | |
| 127 | , coverage: data[num] === undefined | |
| 128 | ? '' | |
| 129 | : data[num] | |
| 130 | }; | |
| 131 | }); | |
| 132 | ||
| 133 | 29 | ret.coverage = ret.hits / ret.sloc * 100; |
| 134 | ||
| 135 | 29 | return ret; |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Return a plain-object representation of `test` | |
| 140 | * free of cyclic properties etc. | |
| 141 | * | |
| 142 | * @param {Object} test | |
| 143 | * @return {Object} | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | function clean(test) { |
| 148 | 0 | return { |
| 149 | title: test.title | |
| 150 | , fullTitle: test.fullTitle() | |
| 151 | , duration: test.duration | |
| 152 | } | |
| 153 | } | |
| 154 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , color = Base.color; | |
| 8 | ||
| 9 | /** | |
| 10 | * Expose `List`. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | exports = module.exports = List; |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a new `List` test reporter. | |
| 17 | * | |
| 18 | * @param {Runner} runner | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function List(runner) { |
| 23 | 0 | Base.call(this, runner); |
| 24 | ||
| 25 | 0 | var self = this |
| 26 | , stats = this.stats | |
| 27 | , total = runner.total; | |
| 28 | ||
| 29 | 0 | runner.on('start', function(){ |
| 30 | 0 | console.log(JSON.stringify(['start', { total: total }])); |
| 31 | }); | |
| 32 | ||
| 33 | 0 | runner.on('pass', function(test){ |
| 34 | 0 | console.log(JSON.stringify(['pass', clean(test)])); |
| 35 | }); | |
| 36 | ||
| 37 | 0 | runner.on('fail', function(test, err){ |
| 38 | 0 | console.log(JSON.stringify(['fail', clean(test)])); |
| 39 | }); | |
| 40 | ||
| 41 | 0 | runner.on('end', function(){ |
| 42 | 0 | process.stdout.write(JSON.stringify(['end', self.stats])); |
| 43 | }); | |
| 44 | } | |
| 45 | ||
| 46 | /** | |
| 47 | * Return a plain-object representation of `test` | |
| 48 | * free of cyclic properties etc. | |
| 49 | * | |
| 50 | * @param {Object} test | |
| 51 | * @return {Object} | |
| 52 | * @api private | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | function clean(test) { |
| 56 | 0 | return { |
| 57 | title: test.title | |
| 58 | , fullTitle: test.fullTitle() | |
| 59 | , duration: test.duration | |
| 60 | } | |
| 61 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `JSON`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = JSONReporter; |
| 15 | ||
| 16 | /** | |
| 17 | * Initialize a new `JSON` reporter. | |
| 18 | * | |
| 19 | * @param {Runner} runner | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | function JSONReporter(runner) { |
| 24 | 0 | var self = this; |
| 25 | 0 | Base.call(this, runner); |
| 26 | ||
| 27 | 0 | var tests = [] |
| 28 | , failures = [] | |
| 29 | , passes = []; | |
| 30 | ||
| 31 | 0 | runner.on('test end', function(test){ |
| 32 | 0 | tests.push(test); |
| 33 | }); | |
| 34 | ||
| 35 | 0 | runner.on('pass', function(test){ |
| 36 | 0 | passes.push(test); |
| 37 | }); | |
| 38 | ||
| 39 | 0 | runner.on('fail', function(test){ |
| 40 | 0 | failures.push(test); |
| 41 | }); | |
| 42 | ||
| 43 | 0 | runner.on('end', function(){ |
| 44 | 0 | var obj = { |
| 45 | stats: self.stats | |
| 46 | , tests: tests.map(clean) | |
| 47 | , failures: failures.map(clean) | |
| 48 | , passes: passes.map(clean) | |
| 49 | }; | |
| 50 | ||
| 51 | 0 | process.stdout.write(JSON.stringify(obj, null, 2)); |
| 52 | }); | |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * Return a plain-object representation of `test` | |
| 57 | * free of cyclic properties etc. | |
| 58 | * | |
| 59 | * @param {Object} test | |
| 60 | * @return {Object} | |
| 61 | * @api private | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | function clean(test) { |
| 65 | 0 | return { |
| 66 | title: test.title | |
| 67 | , fullTitle: test.fullTitle() | |
| 68 | , duration: test.duration | |
| 69 | } | |
| 70 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `Landing`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = Landing; |
| 15 | ||
| 16 | /** | |
| 17 | * Airplane color. | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | Base.colors.plane = 0; |
| 21 | ||
| 22 | /** | |
| 23 | * Airplane crash color. | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | Base.colors['plane crash'] = 31; |
| 27 | ||
| 28 | /** | |
| 29 | * Runway color. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | Base.colors.runway = 90; |
| 33 | ||
| 34 | /** | |
| 35 | * Initialize a new `Landing` reporter. | |
| 36 | * | |
| 37 | * @param {Runner} runner | |
| 38 | * @api public | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | function Landing(runner) { |
| 42 | 0 | Base.call(this, runner); |
| 43 | ||
| 44 | 0 | var self = this |
| 45 | , stats = this.stats | |
| 46 | , width = Base.window.width * .75 | 0 | |
| 47 | , total = runner.total | |
| 48 | , stream = process.stdout | |
| 49 | , plane = color('plane', '✈') | |
| 50 | , crashed = -1 | |
| 51 | , n = 0; | |
| 52 | ||
| 53 | 0 | function runway() { |
| 54 | 0 | var buf = Array(width).join('-'); |
| 55 | 0 | return ' ' + color('runway', buf); |
| 56 | } | |
| 57 | ||
| 58 | 0 | runner.on('start', function(){ |
| 59 | 0 | stream.write('\n '); |
| 60 | 0 | cursor.hide(); |
| 61 | }); | |
| 62 | ||
| 63 | 0 | runner.on('test end', function(test){ |
| 64 | // check if the plane crashed | |
| 65 | 0 | var col = -1 == crashed |
| 66 | ? width * ++n / total | 0 | |
| 67 | : crashed; | |
| 68 | ||
| 69 | // show the crash | |
| 70 | 0 | if ('failed' == test.state) { |
| 71 | 0 | plane = color('plane crash', '✈'); |
| 72 | 0 | crashed = col; |
| 73 | } | |
| 74 | ||
| 75 | // render landing strip | |
| 76 | 0 | stream.write('\u001b[4F\n\n'); |
| 77 | 0 | stream.write(runway()); |
| 78 | 0 | stream.write('\n '); |
| 79 | 0 | stream.write(color('runway', Array(col).join('â‹…'))); |
| 80 | 0 | stream.write(plane) |
| 81 | 0 | stream.write(color('runway', Array(width - col).join('â‹…') + '\n')); |
| 82 | 0 | stream.write(runway()); |
| 83 | 0 | stream.write('\u001b[0m'); |
| 84 | }); | |
| 85 | ||
| 86 | 0 | runner.on('end', function(){ |
| 87 | 0 | cursor.show(); |
| 88 | 0 | console.log(); |
| 89 | 0 | self.epilogue(); |
| 90 | }); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Inherit from `Base.prototype`. | |
| 95 | */ | |
| 96 | ||
| 97 | 1 | Landing.prototype.__proto__ = Base.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `List`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = List; |
| 15 | ||
| 16 | /** | |
| 17 | * Initialize a new `List` test reporter. | |
| 18 | * | |
| 19 | * @param {Runner} runner | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | function List(runner) { |
| 24 | 0 | Base.call(this, runner); |
| 25 | ||
| 26 | 0 | var self = this |
| 27 | , stats = this.stats | |
| 28 | , n = 0; | |
| 29 | ||
| 30 | 0 | runner.on('start', function(){ |
| 31 | 0 | console.log(); |
| 32 | }); | |
| 33 | ||
| 34 | 0 | runner.on('test', function(test){ |
| 35 | 0 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); |
| 36 | }); | |
| 37 | ||
| 38 | 0 | runner.on('pending', function(test){ |
| 39 | 0 | var fmt = color('checkmark', ' -') |
| 40 | + color('pending', ' %s'); | |
| 41 | 0 | console.log(fmt, test.fullTitle()); |
| 42 | }); | |
| 43 | ||
| 44 | 0 | runner.on('pass', function(test){ |
| 45 | 0 | var fmt = color('checkmark', ' '+Base.symbols.dot) |
| 46 | + color('pass', ' %s: ') | |
| 47 | + color(test.speed, '%dms'); | |
| 48 | 0 | cursor.CR(); |
| 49 | 0 | console.log(fmt, test.fullTitle(), test.duration); |
| 50 | }); | |
| 51 | ||
| 52 | 0 | runner.on('fail', function(test, err){ |
| 53 | 0 | cursor.CR(); |
| 54 | 0 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); |
| 55 | }); | |
| 56 | ||
| 57 | 0 | runner.on('end', self.epilogue.bind(self)); |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Inherit from `Base.prototype`. | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | List.prototype.__proto__ = Base.prototype; |
| 65 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Base = require('./base') |
| 6 | , utils = require('../utils'); | |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Markdown`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | exports = module.exports = Markdown; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Markdown` reporter. | |
| 16 | * | |
| 17 | * @param {Runner} runner | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function Markdown(runner) { |
| 22 | 0 | Base.call(this, runner); |
| 23 | ||
| 24 | 0 | var self = this |
| 25 | , stats = this.stats | |
| 26 | , level = 0 | |
| 27 | , buf = ''; | |
| 28 | ||
| 29 | 0 | function title(str) { |
| 30 | 0 | return Array(level).join('#') + ' ' + str; |
| 31 | } | |
| 32 | ||
| 33 | 0 | function indent() { |
| 34 | 0 | return Array(level).join(' '); |
| 35 | } | |
| 36 | ||
| 37 | 0 | function mapTOC(suite, obj) { |
| 38 | 0 | var ret = obj; |
| 39 | 0 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; |
| 40 | 0 | suite.suites.forEach(function(suite){ |
| 41 | 0 | mapTOC(suite, obj); |
| 42 | }); | |
| 43 | 0 | return ret; |
| 44 | } | |
| 45 | ||
| 46 | 0 | function stringifyTOC(obj, level) { |
| 47 | 0 | ++level; |
| 48 | 0 | var buf = ''; |
| 49 | 0 | var link; |
| 50 | 0 | for (var key in obj) { |
| 51 | 0 | if ('suite' == key) continue; |
| 52 | 0 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; |
| 53 | 0 | if (key) buf += Array(level).join(' ') + link; |
| 54 | 0 | buf += stringifyTOC(obj[key], level); |
| 55 | } | |
| 56 | 0 | --level; |
| 57 | 0 | return buf; |
| 58 | } | |
| 59 | ||
| 60 | 0 | function generateTOC(suite) { |
| 61 | 0 | var obj = mapTOC(suite, {}); |
| 62 | 0 | return stringifyTOC(obj, 0); |
| 63 | } | |
| 64 | ||
| 65 | 0 | generateTOC(runner.suite); |
| 66 | ||
| 67 | 0 | runner.on('suite', function(suite){ |
| 68 | 0 | ++level; |
| 69 | 0 | var slug = utils.slug(suite.fullTitle()); |
| 70 | 0 | buf += '<a name="' + slug + '"></a>' + '\n'; |
| 71 | 0 | buf += title(suite.title) + '\n'; |
| 72 | }); | |
| 73 | ||
| 74 | 0 | runner.on('suite end', function(suite){ |
| 75 | 0 | --level; |
| 76 | }); | |
| 77 | ||
| 78 | 0 | runner.on('pass', function(test){ |
| 79 | 0 | var code = utils.clean(test.fn.toString()); |
| 80 | 0 | buf += test.title + '.\n'; |
| 81 | 0 | buf += '\n```js\n'; |
| 82 | 0 | buf += code + '\n'; |
| 83 | 0 | buf += '```\n\n'; |
| 84 | }); | |
| 85 | ||
| 86 | 0 | runner.on('end', function(){ |
| 87 | 0 | process.stdout.write('# TOC\n'); |
| 88 | 0 | process.stdout.write(generateTOC(runner.suite)); |
| 89 | 0 | process.stdout.write(buf); |
| 90 | }); | |
| 91 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base'); |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Min`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | exports = module.exports = Min; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Min` minimal test reporter (best used with --watch). | |
| 16 | * | |
| 17 | * @param {Runner} runner | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function Min(runner) { |
| 22 | 0 | Base.call(this, runner); |
| 23 | ||
| 24 | 0 | runner.on('start', function(){ |
| 25 | // clear screen | |
| 26 | 0 | process.stdout.write('\u001b[2J'); |
| 27 | // set cursor position | |
| 28 | 0 | process.stdout.write('\u001b[1;3H'); |
| 29 | }); | |
| 30 | ||
| 31 | 0 | runner.on('end', this.epilogue.bind(this)); |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Inherit from `Base.prototype`. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | Min.prototype.__proto__ = Base.prototype; |
| 39 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Base = require('./base') |
| 6 | , color = Base.color; | |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Dot`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | exports = module.exports = NyanCat; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Dot` matrix test reporter. | |
| 16 | * | |
| 17 | * @param {Runner} runner | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function NyanCat(runner) { |
| 22 | 0 | Base.call(this, runner); |
| 23 | 0 | var self = this |
| 24 | , stats = this.stats | |
| 25 | , width = Base.window.width * .75 | 0 | |
| 26 | , rainbowColors = this.rainbowColors = self.generateColors() | |
| 27 | , colorIndex = this.colorIndex = 0 | |
| 28 | , numerOfLines = this.numberOfLines = 4 | |
| 29 | , trajectories = this.trajectories = [[], [], [], []] | |
| 30 | , nyanCatWidth = this.nyanCatWidth = 11 | |
| 31 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) | |
| 32 | , scoreboardWidth = this.scoreboardWidth = 5 | |
| 33 | , tick = this.tick = 0 | |
| 34 | , n = 0; | |
| 35 | ||
| 36 | 0 | runner.on('start', function(){ |
| 37 | 0 | Base.cursor.hide(); |
| 38 | 0 | self.draw(); |
| 39 | }); | |
| 40 | ||
| 41 | 0 | runner.on('pending', function(test){ |
| 42 | 0 | self.draw(); |
| 43 | }); | |
| 44 | ||
| 45 | 0 | runner.on('pass', function(test){ |
| 46 | 0 | self.draw(); |
| 47 | }); | |
| 48 | ||
| 49 | 0 | runner.on('fail', function(test, err){ |
| 50 | 0 | self.draw(); |
| 51 | }); | |
| 52 | ||
| 53 | 0 | runner.on('end', function(){ |
| 54 | 0 | Base.cursor.show(); |
| 55 | 0 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); |
| 56 | 0 | self.epilogue(); |
| 57 | }); | |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * Draw the nyan cat | |
| 62 | * | |
| 63 | * @api private | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | NyanCat.prototype.draw = function(){ |
| 67 | 0 | this.appendRainbow(); |
| 68 | 0 | this.drawScoreboard(); |
| 69 | 0 | this.drawRainbow(); |
| 70 | 0 | this.drawNyanCat(); |
| 71 | 0 | this.tick = !this.tick; |
| 72 | }; | |
| 73 | ||
| 74 | /** | |
| 75 | * Draw the "scoreboard" showing the number | |
| 76 | * of passes, failures and pending tests. | |
| 77 | * | |
| 78 | * @api private | |
| 79 | */ | |
| 80 | ||
| 81 | 1 | NyanCat.prototype.drawScoreboard = function(){ |
| 82 | 0 | var stats = this.stats; |
| 83 | 0 | var colors = Base.colors; |
| 84 | ||
| 85 | 0 | function draw(color, n) { |
| 86 | 0 | write(' '); |
| 87 | 0 | write('\u001b[' + color + 'm' + n + '\u001b[0m'); |
| 88 | 0 | write('\n'); |
| 89 | } | |
| 90 | ||
| 91 | 0 | draw(colors.green, stats.passes); |
| 92 | 0 | draw(colors.fail, stats.failures); |
| 93 | 0 | draw(colors.pending, stats.pending); |
| 94 | 0 | write('\n'); |
| 95 | ||
| 96 | 0 | this.cursorUp(this.numberOfLines); |
| 97 | }; | |
| 98 | ||
| 99 | /** | |
| 100 | * Append the rainbow. | |
| 101 | * | |
| 102 | * @api private | |
| 103 | */ | |
| 104 | ||
| 105 | 1 | NyanCat.prototype.appendRainbow = function(){ |
| 106 | 0 | var segment = this.tick ? '_' : '-'; |
| 107 | 0 | var rainbowified = this.rainbowify(segment); |
| 108 | ||
| 109 | 0 | for (var index = 0; index < this.numberOfLines; index++) { |
| 110 | 0 | var trajectory = this.trajectories[index]; |
| 111 | 0 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); |
| 112 | 0 | trajectory.push(rainbowified); |
| 113 | } | |
| 114 | }; | |
| 115 | ||
| 116 | /** | |
| 117 | * Draw the rainbow. | |
| 118 | * | |
| 119 | * @api private | |
| 120 | */ | |
| 121 | ||
| 122 | 1 | NyanCat.prototype.drawRainbow = function(){ |
| 123 | 0 | var self = this; |
| 124 | ||
| 125 | 0 | this.trajectories.forEach(function(line, index) { |
| 126 | 0 | write('\u001b[' + self.scoreboardWidth + 'C'); |
| 127 | 0 | write(line.join('')); |
| 128 | 0 | write('\n'); |
| 129 | }); | |
| 130 | ||
| 131 | 0 | this.cursorUp(this.numberOfLines); |
| 132 | }; | |
| 133 | ||
| 134 | /** | |
| 135 | * Draw the nyan cat | |
| 136 | * | |
| 137 | * @api private | |
| 138 | */ | |
| 139 | ||
| 140 | 1 | NyanCat.prototype.drawNyanCat = function() { |
| 141 | 0 | var self = this; |
| 142 | 0 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; |
| 143 | 0 | var color = '\u001b[' + startWidth + 'C'; |
| 144 | 0 | var padding = ''; |
| 145 | ||
| 146 | 0 | write(color); |
| 147 | 0 | write('_,------,'); |
| 148 | 0 | write('\n'); |
| 149 | ||
| 150 | 0 | write(color); |
| 151 | 0 | padding = self.tick ? ' ' : ' '; |
| 152 | 0 | write('_|' + padding + '/\\_/\\ '); |
| 153 | 0 | write('\n'); |
| 154 | ||
| 155 | 0 | write(color); |
| 156 | 0 | padding = self.tick ? '_' : '__'; |
| 157 | 0 | var tail = self.tick ? '~' : '^'; |
| 158 | 0 | var face; |
| 159 | 0 | write(tail + '|' + padding + this.face() + ' '); |
| 160 | 0 | write('\n'); |
| 161 | ||
| 162 | 0 | write(color); |
| 163 | 0 | padding = self.tick ? ' ' : ' '; |
| 164 | 0 | write(padding + '"" "" '); |
| 165 | 0 | write('\n'); |
| 166 | ||
| 167 | 0 | this.cursorUp(this.numberOfLines); |
| 168 | }; | |
| 169 | ||
| 170 | /** | |
| 171 | * Draw nyan cat face. | |
| 172 | * | |
| 173 | * @return {String} | |
| 174 | * @api private | |
| 175 | */ | |
| 176 | ||
| 177 | 1 | NyanCat.prototype.face = function() { |
| 178 | 0 | var stats = this.stats; |
| 179 | 0 | if (stats.failures) { |
| 180 | 0 | return '( x .x)'; |
| 181 | 0 | } else if (stats.pending) { |
| 182 | 0 | return '( o .o)'; |
| 183 | 0 | } else if(stats.passes) { |
| 184 | 0 | return '( ^ .^)'; |
| 185 | } else { | |
| 186 | 0 | return '( - .-)'; |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Move cursor up `n`. | |
| 192 | * | |
| 193 | * @param {Number} n | |
| 194 | * @api private | |
| 195 | */ | |
| 196 | ||
| 197 | 1 | NyanCat.prototype.cursorUp = function(n) { |
| 198 | 0 | write('\u001b[' + n + 'A'); |
| 199 | }; | |
| 200 | ||
| 201 | /** | |
| 202 | * Move cursor down `n`. | |
| 203 | * | |
| 204 | * @param {Number} n | |
| 205 | * @api private | |
| 206 | */ | |
| 207 | ||
| 208 | 1 | NyanCat.prototype.cursorDown = function(n) { |
| 209 | 0 | write('\u001b[' + n + 'B'); |
| 210 | }; | |
| 211 | ||
| 212 | /** | |
| 213 | * Generate rainbow colors. | |
| 214 | * | |
| 215 | * @return {Array} | |
| 216 | * @api private | |
| 217 | */ | |
| 218 | ||
| 219 | 1 | NyanCat.prototype.generateColors = function(){ |
| 220 | 0 | var colors = []; |
| 221 | ||
| 222 | 0 | for (var i = 0; i < (6 * 7); i++) { |
| 223 | 0 | var pi3 = Math.floor(Math.PI / 3); |
| 224 | 0 | var n = (i * (1.0 / 6)); |
| 225 | 0 | var r = Math.floor(3 * Math.sin(n) + 3); |
| 226 | 0 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); |
| 227 | 0 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); |
| 228 | 0 | colors.push(36 * r + 6 * g + b + 16); |
| 229 | } | |
| 230 | ||
| 231 | 0 | return colors; |
| 232 | }; | |
| 233 | ||
| 234 | /** | |
| 235 | * Apply rainbow to the given `str`. | |
| 236 | * | |
| 237 | * @param {String} str | |
| 238 | * @return {String} | |
| 239 | * @api private | |
| 240 | */ | |
| 241 | ||
| 242 | 1 | NyanCat.prototype.rainbowify = function(str){ |
| 243 | 0 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; |
| 244 | 0 | this.colorIndex += 1; |
| 245 | 0 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; |
| 246 | }; | |
| 247 | ||
| 248 | /** | |
| 249 | * Stdout helper. | |
| 250 | */ | |
| 251 | ||
| 252 | 1 | function write(string) { |
| 253 | 0 | process.stdout.write(string); |
| 254 | } | |
| 255 | ||
| 256 | /** | |
| 257 | * Inherit from `Base.prototype`. | |
| 258 | */ | |
| 259 | ||
| 260 | 1 | NyanCat.prototype.__proto__ = Base.prototype; |
| 261 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `Progress`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = Progress; |
| 15 | ||
| 16 | /** | |
| 17 | * General progress bar color. | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | Base.colors.progress = 90; |
| 21 | ||
| 22 | /** | |
| 23 | * Initialize a new `Progress` bar test reporter. | |
| 24 | * | |
| 25 | * @param {Runner} runner | |
| 26 | * @param {Object} options | |
| 27 | * @api public | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | function Progress(runner, options) { |
| 31 | 0 | Base.call(this, runner); |
| 32 | ||
| 33 | 0 | var self = this |
| 34 | , options = options || {} | |
| 35 | , stats = this.stats | |
| 36 | , width = Base.window.width * .50 | 0 | |
| 37 | , total = runner.total | |
| 38 | , complete = 0 | |
| 39 | , max = Math.max; | |
| 40 | ||
| 41 | // default chars | |
| 42 | 0 | options.open = options.open || '['; |
| 43 | 0 | options.complete = options.complete || 'â–¬'; |
| 44 | 0 | options.incomplete = options.incomplete || Base.symbols.dot; |
| 45 | 0 | options.close = options.close || ']'; |
| 46 | 0 | options.verbose = false; |
| 47 | ||
| 48 | // tests started | |
| 49 | 0 | runner.on('start', function(){ |
| 50 | 0 | console.log(); |
| 51 | 0 | cursor.hide(); |
| 52 | }); | |
| 53 | ||
| 54 | // tests complete | |
| 55 | 0 | runner.on('test end', function(){ |
| 56 | 0 | complete++; |
| 57 | 0 | var incomplete = total - complete |
| 58 | , percent = complete / total | |
| 59 | , n = width * percent | 0 | |
| 60 | , i = width - n; | |
| 61 | ||
| 62 | 0 | cursor.CR(); |
| 63 | 0 | process.stdout.write('\u001b[J'); |
| 64 | 0 | process.stdout.write(color('progress', ' ' + options.open)); |
| 65 | 0 | process.stdout.write(Array(n).join(options.complete)); |
| 66 | 0 | process.stdout.write(Array(i).join(options.incomplete)); |
| 67 | 0 | process.stdout.write(color('progress', options.close)); |
| 68 | 0 | if (options.verbose) { |
| 69 | 0 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); |
| 70 | } | |
| 71 | }); | |
| 72 | ||
| 73 | // tests are complete, output some stats | |
| 74 | // and the failures if any | |
| 75 | 0 | runner.on('end', function(){ |
| 76 | 0 | cursor.show(); |
| 77 | 0 | console.log(); |
| 78 | 0 | self.epilogue(); |
| 79 | }); | |
| 80 | } | |
| 81 | ||
| 82 | /** | |
| 83 | * Inherit from `Base.prototype`. | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | Progress.prototype.__proto__ = Base.prototype; |
| 87 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `Spec`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = Spec; |
| 15 | ||
| 16 | /** | |
| 17 | * Initialize a new `Spec` test reporter. | |
| 18 | * | |
| 19 | * @param {Runner} runner | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | function Spec(runner) { |
| 24 | 1 | Base.call(this, runner); |
| 25 | ||
| 26 | 1 | var self = this |
| 27 | , stats = this.stats | |
| 28 | , indents = 0 | |
| 29 | , n = 0; | |
| 30 | ||
| 31 | 1 | function indent() { |
| 32 | 6 | return Array(indents).join(' ') |
| 33 | } | |
| 34 | ||
| 35 | 1 | runner.on('start', function(){ |
| 36 | 1 | console.log(); |
| 37 | }); | |
| 38 | ||
| 39 | 1 | runner.on('suite', function(suite){ |
| 40 | 4 | ++indents; |
| 41 | 4 | console.log(color('suite', '%s%s'), indent(), suite.title); |
| 42 | }); | |
| 43 | ||
| 44 | 1 | runner.on('suite end', function(suite){ |
| 45 | 4 | --indents; |
| 46 | 6 | if (1 == indents) console.log(); |
| 47 | }); | |
| 48 | ||
| 49 | 1 | runner.on('pending', function(test){ |
| 50 | 1 | var fmt = indent() + color('pending', ' - %s'); |
| 51 | 1 | console.log(fmt, test.title); |
| 52 | }); | |
| 53 | ||
| 54 | 1 | runner.on('pass', function(test){ |
| 55 | 1 | if ('fast' == test.speed) { |
| 56 | 1 | var fmt = indent() |
| 57 | + color('checkmark', ' ' + Base.symbols.ok) | |
| 58 | + color('pass', ' %s '); | |
| 59 | 1 | cursor.CR(); |
| 60 | 1 | console.log(fmt, test.title); |
| 61 | } else { | |
| 62 | 0 | var fmt = indent() |
| 63 | + color('checkmark', ' ' + Base.symbols.ok) | |
| 64 | + color('pass', ' %s ') | |
| 65 | + color(test.speed, '(%dms)'); | |
| 66 | 0 | cursor.CR(); |
| 67 | 0 | console.log(fmt, test.title, test.duration); |
| 68 | } | |
| 69 | }); | |
| 70 | ||
| 71 | 1 | runner.on('fail', function(test, err){ |
| 72 | 0 | cursor.CR(); |
| 73 | 0 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); |
| 74 | }); | |
| 75 | ||
| 76 | 1 | runner.on('end', self.epilogue.bind(self)); |
| 77 | } | |
| 78 | ||
| 79 | /** | |
| 80 | * Inherit from `Base.prototype`. | |
| 81 | */ | |
| 82 | ||
| 83 | 1 | Spec.prototype.__proto__ = Base.prototype; |
| 84 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , cursor = Base.cursor | |
| 8 | , color = Base.color; | |
| 9 | ||
| 10 | /** | |
| 11 | * Expose `TAP`. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | exports = module.exports = TAP; |
| 15 | ||
| 16 | /** | |
| 17 | * Initialize a new `TAP` reporter. | |
| 18 | * | |
| 19 | * @param {Runner} runner | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | function TAP(runner) { |
| 24 | 0 | Base.call(this, runner); |
| 25 | ||
| 26 | 0 | var self = this |
| 27 | , stats = this.stats | |
| 28 | , n = 1 | |
| 29 | , passes = 0 | |
| 30 | , failures = 0; | |
| 31 | ||
| 32 | 0 | runner.on('start', function(){ |
| 33 | 0 | var total = runner.grepTotal(runner.suite); |
| 34 | 0 | console.log('%d..%d', 1, total); |
| 35 | }); | |
| 36 | ||
| 37 | 0 | runner.on('test end', function(){ |
| 38 | 0 | ++n; |
| 39 | }); | |
| 40 | ||
| 41 | 0 | runner.on('pending', function(test){ |
| 42 | 0 | console.log('ok %d %s # SKIP -', n, title(test)); |
| 43 | }); | |
| 44 | ||
| 45 | 0 | runner.on('pass', function(test){ |
| 46 | 0 | passes++; |
| 47 | 0 | console.log('ok %d %s', n, title(test)); |
| 48 | }); | |
| 49 | ||
| 50 | 0 | runner.on('fail', function(test, err){ |
| 51 | 0 | failures++; |
| 52 | 0 | console.log('not ok %d %s', n, title(test)); |
| 53 | 0 | if (err.stack) console.log(err.stack.replace(/^/gm, ' ')); |
| 54 | }); | |
| 55 | ||
| 56 | 0 | runner.on('end', function(){ |
| 57 | 0 | console.log('# tests ' + (passes + failures)); |
| 58 | 0 | console.log('# pass ' + passes); |
| 59 | 0 | console.log('# fail ' + failures); |
| 60 | }); | |
| 61 | } | |
| 62 | ||
| 63 | /** | |
| 64 | * Return a TAP-safe title of `test` | |
| 65 | * | |
| 66 | * @param {Object} test | |
| 67 | * @return {String} | |
| 68 | * @api private | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | function title(test) { |
| 72 | 0 | return test.fullTitle().replace(/#/g, ''); |
| 73 | } | |
| 74 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Base = require('./base') |
| 7 | , utils = require('../utils') | |
| 8 | , escape = utils.escape; | |
| 9 | ||
| 10 | /** | |
| 11 | * Save timer references to avoid Sinon interfering (see GH-237). | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | var Date = global.Date |
| 15 | , setTimeout = global.setTimeout | |
| 16 | , setInterval = global.setInterval | |
| 17 | , clearTimeout = global.clearTimeout | |
| 18 | , clearInterval = global.clearInterval; | |
| 19 | ||
| 20 | /** | |
| 21 | * Expose `XUnit`. | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | exports = module.exports = XUnit; |
| 25 | ||
| 26 | /** | |
| 27 | * Initialize a new `XUnit` reporter. | |
| 28 | * | |
| 29 | * @param {Runner} runner | |
| 30 | * @api public | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | function XUnit(runner) { |
| 34 | 0 | Base.call(this, runner); |
| 35 | 0 | var stats = this.stats |
| 36 | , tests = [] | |
| 37 | , self = this; | |
| 38 | ||
| 39 | 0 | runner.on('pending', function(test){ |
| 40 | 0 | tests.push(test); |
| 41 | }); | |
| 42 | ||
| 43 | 0 | runner.on('pass', function(test){ |
| 44 | 0 | tests.push(test); |
| 45 | }); | |
| 46 | ||
| 47 | 0 | runner.on('fail', function(test){ |
| 48 | 0 | tests.push(test); |
| 49 | }); | |
| 50 | ||
| 51 | 0 | runner.on('end', function(){ |
| 52 | 0 | console.log(tag('testsuite', { |
| 53 | name: 'Mocha Tests' | |
| 54 | , tests: stats.tests | |
| 55 | , failures: stats.failures | |
| 56 | , errors: stats.failures | |
| 57 | , skipped: stats.tests - stats.failures - stats.passes | |
| 58 | , timestamp: (new Date).toUTCString() | |
| 59 | , time: (stats.duration / 1000) || 0 | |
| 60 | }, false)); | |
| 61 | ||
| 62 | 0 | tests.forEach(test); |
| 63 | 0 | console.log('</testsuite>'); |
| 64 | }); | |
| 65 | } | |
| 66 | ||
| 67 | /** | |
| 68 | * Inherit from `Base.prototype`. | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | XUnit.prototype.__proto__ = Base.prototype; |
| 72 | ||
| 73 | /** | |
| 74 | * Output tag for the given `test.` | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | function test(test) { |
| 78 | 0 | var attrs = { |
| 79 | classname: test.parent.fullTitle() | |
| 80 | , name: test.title | |
| 81 | , time: (test.duration / 1000) || 0 | |
| 82 | }; | |
| 83 | ||
| 84 | 0 | if ('failed' == test.state) { |
| 85 | 0 | var err = test.err; |
| 86 | 0 | attrs.message = escape(err.message); |
| 87 | 0 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); |
| 88 | 0 | } else if (test.pending) { |
| 89 | 0 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); |
| 90 | } else { | |
| 91 | 0 | console.log(tag('testcase', attrs, true) ); |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * HTML tag helper. | |
| 97 | */ | |
| 98 | ||
| 99 | 1 | function tag(name, attrs, close, content) { |
| 100 | 0 | var end = close ? '/>' : '>' |
| 101 | , pairs = [] | |
| 102 | , tag; | |
| 103 | ||
| 104 | 0 | for (var key in attrs) { |
| 105 | 0 | pairs.push(key + '="' + escape(attrs[key]) + '"'); |
| 106 | } | |
| 107 | ||
| 108 | 0 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; |
| 109 | 0 | if (content) tag += content + '</' + name + end; |
| 110 | 0 | return tag; |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Return cdata escaped CDATA `str`. | |
| 115 | */ | |
| 116 | ||
| 117 | 1 | function cdata(str) { |
| 118 | 0 | return '<![CDATA[' + escape(str) + ']]>'; |
| 119 | } | |
| 120 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var EventEmitter = require('events').EventEmitter |
| 7 | , debug = require('debug')('mocha:runnable') | |
| 8 | , milliseconds = require('./ms'); | |
| 9 | ||
| 10 | /** | |
| 11 | * Save timer references to avoid Sinon interfering (see GH-237). | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | var Date = global.Date |
| 15 | , setTimeout = global.setTimeout | |
| 16 | , setInterval = global.setInterval | |
| 17 | , clearTimeout = global.clearTimeout | |
| 18 | , clearInterval = global.clearInterval; | |
| 19 | ||
| 20 | /** | |
| 21 | * Object#toString(). | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var toString = Object.prototype.toString; |
| 25 | ||
| 26 | /** | |
| 27 | * Expose `Runnable`. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | module.exports = Runnable; |
| 31 | ||
| 32 | /** | |
| 33 | * Initialize a new `Runnable` with the given `title` and callback `fn`. | |
| 34 | * | |
| 35 | * @param {String} title | |
| 36 | * @param {Function} fn | |
| 37 | * @api private | |
| 38 | */ | |
| 39 | ||
| 40 | 1 | function Runnable(title, fn) { |
| 41 | 4 | this.title = title; |
| 42 | 4 | this.fn = fn; |
| 43 | 4 | this.async = fn && fn.length; |
| 44 | 4 | this.sync = ! this.async; |
| 45 | 4 | this._timeout = 2000; |
| 46 | 4 | this._slow = 75; |
| 47 | 4 | this.timedOut = false; |
| 48 | } | |
| 49 | ||
| 50 | /** | |
| 51 | * Inherit from `EventEmitter.prototype`. | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | Runnable.prototype.__proto__ = EventEmitter.prototype; |
| 55 | ||
| 56 | /** | |
| 57 | * Set & get timeout `ms`. | |
| 58 | * | |
| 59 | * @param {Number|String} ms | |
| 60 | * @return {Runnable|Number} ms or self | |
| 61 | * @api private | |
| 62 | */ | |
| 63 | ||
| 64 | 1 | Runnable.prototype.timeout = function(ms){ |
| 65 | 10 | if (0 == arguments.length) return this._timeout; |
| 66 | 4 | if ('string' == typeof ms) ms = milliseconds(ms); |
| 67 | 4 | debug('timeout %d', ms); |
| 68 | 4 | this._timeout = ms; |
| 69 | 4 | if (this.timer) this.resetTimeout(); |
| 70 | 4 | return this; |
| 71 | }; | |
| 72 | ||
| 73 | /** | |
| 74 | * Set & get slow `ms`. | |
| 75 | * | |
| 76 | * @param {Number|String} ms | |
| 77 | * @return {Runnable|Number} ms or self | |
| 78 | * @api private | |
| 79 | */ | |
| 80 | ||
| 81 | 1 | Runnable.prototype.slow = function(ms){ |
| 82 | 8 | if (0 === arguments.length) return this._slow; |
| 83 | 4 | if ('string' == typeof ms) ms = milliseconds(ms); |
| 84 | 4 | debug('timeout %d', ms); |
| 85 | 4 | this._slow = ms; |
| 86 | 4 | return this; |
| 87 | }; | |
| 88 | ||
| 89 | /** | |
| 90 | * Return the full title generated by recursively | |
| 91 | * concatenating the parent's full title. | |
| 92 | * | |
| 93 | * @return {String} | |
| 94 | * @api public | |
| 95 | */ | |
| 96 | ||
| 97 | 1 | Runnable.prototype.fullTitle = function(){ |
| 98 | 9 | return this.parent.fullTitle() + ' ' + this.title; |
| 99 | }; | |
| 100 | ||
| 101 | /** | |
| 102 | * Clear the timeout. | |
| 103 | * | |
| 104 | * @api private | |
| 105 | */ | |
| 106 | ||
| 107 | 1 | Runnable.prototype.clearTimeout = function(){ |
| 108 | 1 | clearTimeout(this.timer); |
| 109 | }; | |
| 110 | ||
| 111 | /** | |
| 112 | * Inspect the runnable void of private properties. | |
| 113 | * | |
| 114 | * @return {String} | |
| 115 | * @api private | |
| 116 | */ | |
| 117 | ||
| 118 | 1 | Runnable.prototype.inspect = function(){ |
| 119 | 0 | return JSON.stringify(this, function(key, val){ |
| 120 | 0 | if ('_' == key[0]) return; |
| 121 | 0 | if ('parent' == key) return '#<Suite>'; |
| 122 | 0 | if ('ctx' == key) return '#<Context>'; |
| 123 | 0 | return val; |
| 124 | }, 2); | |
| 125 | }; | |
| 126 | ||
| 127 | /** | |
| 128 | * Reset the timeout. | |
| 129 | * | |
| 130 | * @api private | |
| 131 | */ | |
| 132 | ||
| 133 | 1 | Runnable.prototype.resetTimeout = function(){ |
| 134 | 0 | var self = this; |
| 135 | 0 | var ms = this.timeout() || 1e9; |
| 136 | ||
| 137 | 0 | this.clearTimeout(); |
| 138 | 0 | this.timer = setTimeout(function(){ |
| 139 | 0 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); |
| 140 | 0 | self.timedOut = true; |
| 141 | }, ms); | |
| 142 | }; | |
| 143 | ||
| 144 | /** | |
| 145 | * Whitelist these globals for this test run | |
| 146 | * | |
| 147 | * @api private | |
| 148 | */ | |
| 149 | 1 | Runnable.prototype.globals = function(arr){ |
| 150 | 0 | var self = this; |
| 151 | 0 | this._allowedGlobals = arr; |
| 152 | }; | |
| 153 | ||
| 154 | /** | |
| 155 | * Run the test and invoke `fn(err)`. | |
| 156 | * | |
| 157 | * @param {Function} fn | |
| 158 | * @api private | |
| 159 | */ | |
| 160 | ||
| 161 | 1 | Runnable.prototype.run = function(fn){ |
| 162 | 3 | var self = this |
| 163 | , ms = this.timeout() | |
| 164 | , start = new Date | |
| 165 | , ctx = this.ctx | |
| 166 | , finished | |
| 167 | , emitted; | |
| 168 | ||
| 169 | 6 | if (ctx) ctx.runnable(this); |
| 170 | ||
| 171 | // timeout | |
| 172 | 3 | if (this.async) { |
| 173 | 1 | if (ms) { |
| 174 | 1 | this.timer = setTimeout(function(){ |
| 175 | 0 | done(new Error('timeout of ' + ms + 'ms exceeded')); |
| 176 | 0 | self.timedOut = true; |
| 177 | }, ms); | |
| 178 | } | |
| 179 | } | |
| 180 | ||
| 181 | // called multiple times | |
| 182 | 3 | function multiple(err) { |
| 183 | 0 | if (emitted) return; |
| 184 | 0 | emitted = true; |
| 185 | 0 | self.emit('error', err || new Error('done() called multiple times')); |
| 186 | } | |
| 187 | ||
| 188 | // finished | |
| 189 | 3 | function done(err) { |
| 190 | 1 | if (self.timedOut) return; |
| 191 | 1 | if (finished) return multiple(err); |
| 192 | 1 | self.clearTimeout(); |
| 193 | 1 | self.duration = new Date - start; |
| 194 | 1 | finished = true; |
| 195 | 1 | fn(err); |
| 196 | } | |
| 197 | ||
| 198 | // for .resetTimeout() | |
| 199 | 3 | this.callback = done; |
| 200 | ||
| 201 | // async | |
| 202 | 3 | if (this.async) { |
| 203 | 1 | try { |
| 204 | 1 | this.fn.call(ctx, function(err){ |
| 205 | 1 | if (err instanceof Error || toString.call(err) === "[object Error]") return done(err); |
| 206 | 1 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); |
| 207 | 1 | done(); |
| 208 | }); | |
| 209 | } catch (err) { | |
| 210 | 0 | done(err); |
| 211 | } | |
| 212 | 1 | return; |
| 213 | } | |
| 214 | ||
| 215 | 2 | if (this.asyncOnly) { |
| 216 | 0 | return done(new Error('--async-only option in use without declaring `done()`')); |
| 217 | } | |
| 218 | ||
| 219 | // sync | |
| 220 | 2 | try { |
| 221 | 4 | if (!this.pending) this.fn.call(ctx); |
| 222 | 2 | this.duration = new Date - start; |
| 223 | 2 | fn(); |
| 224 | } catch (err) { | |
| 225 | 0 | fn(err); |
| 226 | } | |
| 227 | }; | |
| 228 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var EventEmitter = require('events').EventEmitter |
| 6 | , debug = require('debug')('mocha:runner') | |
| 7 | , Test = require('./test') | |
| 8 | , utils = require('./utils') | |
| 9 | , filter = utils.filter | |
| 10 | , keys = utils.keys; | |
| 11 | ||
| 12 | /** | |
| 13 | * Non-enumerable globals. | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | var globals = [ |
| 17 | 'setTimeout', | |
| 18 | 'clearTimeout', | |
| 19 | 'setInterval', | |
| 20 | 'clearInterval', | |
| 21 | 'XMLHttpRequest', | |
| 22 | 'Date' | |
| 23 | ]; | |
| 24 | ||
| 25 | /** | |
| 26 | * Expose `Runner`. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | module.exports = Runner; |
| 30 | ||
| 31 | /** | |
| 32 | * Initialize a `Runner` for the given `suite`. | |
| 33 | * | |
| 34 | * Events: | |
| 35 | * | |
| 36 | * - `start` execution started | |
| 37 | * - `end` execution complete | |
| 38 | * - `suite` (suite) test suite execution started | |
| 39 | * - `suite end` (suite) all tests (and sub-suites) have finished | |
| 40 | * - `test` (test) test execution started | |
| 41 | * - `test end` (test) test completed | |
| 42 | * - `hook` (hook) hook execution started | |
| 43 | * - `hook end` (hook) hook complete | |
| 44 | * - `pass` (test) test passed | |
| 45 | * - `fail` (test, err) test failed | |
| 46 | * - `pending` (test) test pending | |
| 47 | * | |
| 48 | * @api public | |
| 49 | */ | |
| 50 | ||
| 51 | 1 | function Runner(suite) { |
| 52 | 2 | var self = this; |
| 53 | 2 | this._globals = []; |
| 54 | 2 | this._abort = false; |
| 55 | 2 | this.suite = suite; |
| 56 | 2 | this.total = suite.total(); |
| 57 | 2 | this.failures = 0; |
| 58 | 4 | this.on('test end', function(test){ self.checkGlobals(test); }); |
| 59 | 4 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); |
| 60 | 2 | this.grep(/.*/); |
| 61 | 2 | this.globals(this.globalProps().concat(extraGlobals())); |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Wrapper for setImmediate, process.nextTick, or browser polyfill. | |
| 66 | * | |
| 67 | * @param {Function} fn | |
| 68 | * @api private | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | Runner.immediately = global.setImmediate || process.nextTick; |
| 72 | ||
| 73 | /** | |
| 74 | * Inherit from `EventEmitter.prototype`. | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | Runner.prototype.__proto__ = EventEmitter.prototype; |
| 78 | ||
| 79 | /** | |
| 80 | * Run tests with full titles matching `re`. Updates runner.total | |
| 81 | * with number of tests matched. | |
| 82 | * | |
| 83 | * @param {RegExp} re | |
| 84 | * @param {Boolean} invert | |
| 85 | * @return {Runner} for chaining | |
| 86 | * @api public | |
| 87 | */ | |
| 88 | ||
| 89 | 1 | Runner.prototype.grep = function(re, invert){ |
| 90 | 2 | debug('grep %s', re); |
| 91 | 2 | this._grep = re; |
| 92 | 2 | this._invert = invert; |
| 93 | 2 | this.total = this.grepTotal(this.suite); |
| 94 | 2 | return this; |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Returns the number of tests matching the grep search for the | |
| 99 | * given suite. | |
| 100 | * | |
| 101 | * @param {Suite} suite | |
| 102 | * @return {Number} | |
| 103 | * @api public | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | Runner.prototype.grepTotal = function(suite) { |
| 107 | 7 | var self = this; |
| 108 | 7 | var total = 0; |
| 109 | ||
| 110 | 7 | suite.eachTest(function(test){ |
| 111 | 7 | var match = self._grep.test(test.fullTitle()); |
| 112 | 7 | if (self._invert) match = !match; |
| 113 | 14 | if (match) total++; |
| 114 | }); | |
| 115 | ||
| 116 | 7 | return total; |
| 117 | }; | |
| 118 | ||
| 119 | /** | |
| 120 | * Return a list of global properties. | |
| 121 | * | |
| 122 | * @return {Array} | |
| 123 | * @api private | |
| 124 | */ | |
| 125 | ||
| 126 | 1 | Runner.prototype.globalProps = function() { |
| 127 | 6 | var props = utils.keys(global); |
| 128 | ||
| 129 | // non-enumerables | |
| 130 | 6 | for (var i = 0; i < globals.length; ++i) { |
| 131 | 60 | if (~utils.indexOf(props, globals[i])) continue; |
| 132 | 12 | props.push(globals[i]); |
| 133 | } | |
| 134 | ||
| 135 | 6 | return props; |
| 136 | }; | |
| 137 | ||
| 138 | /** | |
| 139 | * Allow the given `arr` of globals. | |
| 140 | * | |
| 141 | * @param {Array} arr | |
| 142 | * @return {Runner} for chaining | |
| 143 | * @api public | |
| 144 | */ | |
| 145 | ||
| 146 | 1 | Runner.prototype.globals = function(arr){ |
| 147 | 3 | if (0 == arguments.length) return this._globals; |
| 148 | 3 | debug('globals %j', arr); |
| 149 | 3 | this._globals = this._globals.concat(arr); |
| 150 | 3 | return this; |
| 151 | }; | |
| 152 | ||
| 153 | /** | |
| 154 | * Check for global variable leaks. | |
| 155 | * | |
| 156 | * @api private | |
| 157 | */ | |
| 158 | ||
| 159 | 1 | Runner.prototype.checkGlobals = function(test){ |
| 160 | 4 | if (this.ignoreLeaks) return; |
| 161 | 4 | var ok = this._globals; |
| 162 | ||
| 163 | 4 | var globals = this.globalProps(); |
| 164 | 4 | var isNode = process.kill; |
| 165 | 4 | var leaks; |
| 166 | ||
| 167 | 4 | if (test) { |
| 168 | 4 | ok = ok.concat(test._allowedGlobals || []); |
| 169 | } | |
| 170 | ||
| 171 | 7 | if(this.prevGlobalsLength == globals.length) return; |
| 172 | 1 | this.prevGlobalsLength = globals.length; |
| 173 | ||
| 174 | 1 | leaks = filterLeaks(ok, globals); |
| 175 | 1 | this._globals = this._globals.concat(leaks); |
| 176 | ||
| 177 | 1 | if (leaks.length > 1) { |
| 178 | 0 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); |
| 179 | 1 | } else if (leaks.length) { |
| 180 | 0 | this.fail(test, new Error('global leak detected: ' + leaks[0])); |
| 181 | } | |
| 182 | }; | |
| 183 | ||
| 184 | /** | |
| 185 | * Fail the given `test`. | |
| 186 | * | |
| 187 | * @param {Test} test | |
| 188 | * @param {Error} err | |
| 189 | * @api private | |
| 190 | */ | |
| 191 | ||
| 192 | 1 | Runner.prototype.fail = function(test, err){ |
| 193 | 0 | ++this.failures; |
| 194 | 0 | test.state = 'failed'; |
| 195 | ||
| 196 | 0 | if ('string' == typeof err) { |
| 197 | 0 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); |
| 198 | } | |
| 199 | ||
| 200 | 0 | this.emit('fail', test, err); |
| 201 | }; | |
| 202 | ||
| 203 | /** | |
| 204 | * Fail the given `hook` with `err`. | |
| 205 | * | |
| 206 | * Hook failures work in the following pattern: | |
| 207 | * - If bail, then exit | |
| 208 | * - Failed `before` hook skips all tests in a suite and subsuites, | |
| 209 | * but jumps to corresponding `after` hook | |
| 210 | * - Failed `before each` hook skips remaining tests in a | |
| 211 | * suite and jumps to corresponding `after each` hook, | |
| 212 | * which is run only once | |
| 213 | * - Failed `after` hook does not alter | |
| 214 | * execution order | |
| 215 | * - Failed `after each` hook skips remaining tests in a | |
| 216 | * suite and subsuites, but executes other `after each` | |
| 217 | * hooks | |
| 218 | * | |
| 219 | * @param {Hook} hook | |
| 220 | * @param {Error} err | |
| 221 | * @api private | |
| 222 | */ | |
| 223 | ||
| 224 | 1 | Runner.prototype.failHook = function(hook, err){ |
| 225 | 0 | this.fail(hook, err); |
| 226 | 0 | if (this.suite.bail()) { |
| 227 | 0 | this.emit('end'); |
| 228 | } | |
| 229 | }; | |
| 230 | ||
| 231 | /** | |
| 232 | * Run hook `name` callbacks and then invoke `fn()`. | |
| 233 | * | |
| 234 | * @param {String} name | |
| 235 | * @param {Function} function | |
| 236 | * @api private | |
| 237 | */ | |
| 238 | ||
| 239 | 1 | Runner.prototype.hook = function(name, fn){ |
| 240 | 14 | var suite = this.suite |
| 241 | , hooks = suite['_' + name] | |
| 242 | , self = this | |
| 243 | , timer; | |
| 244 | ||
| 245 | 14 | function next(i) { |
| 246 | 16 | var hook = hooks[i]; |
| 247 | 30 | if (!hook) return fn(); |
| 248 | 2 | if (self.failures && suite.bail()) return fn(); |
| 249 | 2 | self.currentRunnable = hook; |
| 250 | ||
| 251 | 2 | hook.ctx.currentTest = self.test; |
| 252 | ||
| 253 | 2 | self.emit('hook', hook); |
| 254 | ||
| 255 | 2 | hook.on('error', function(err){ |
| 256 | 0 | self.failHook(hook, err); |
| 257 | }); | |
| 258 | ||
| 259 | 2 | hook.run(function(err){ |
| 260 | 2 | hook.removeAllListeners('error'); |
| 261 | 2 | var testError = hook.error(); |
| 262 | 2 | if (testError) self.fail(self.test, testError); |
| 263 | 2 | if (err) { |
| 264 | 0 | self.failHook(hook, err); |
| 265 | ||
| 266 | // stop executing hooks, notify callee of hook err | |
| 267 | 0 | return fn(err); |
| 268 | } | |
| 269 | 2 | self.emit('hook end', hook); |
| 270 | 2 | delete hook.ctx.currentTest; |
| 271 | 2 | next(++i); |
| 272 | }); | |
| 273 | } | |
| 274 | ||
| 275 | 14 | Runner.immediately(function(){ |
| 276 | 14 | next(0); |
| 277 | }); | |
| 278 | }; | |
| 279 | ||
| 280 | /** | |
| 281 | * Run hook `name` for the given array of `suites` | |
| 282 | * in order, and callback `fn(err, errSuite)`. | |
| 283 | * | |
| 284 | * @param {String} name | |
| 285 | * @param {Array} suites | |
| 286 | * @param {Function} fn | |
| 287 | * @api private | |
| 288 | */ | |
| 289 | ||
| 290 | 1 | Runner.prototype.hooks = function(name, suites, fn){ |
| 291 | 2 | var self = this |
| 292 | , orig = this.suite; | |
| 293 | ||
| 294 | 2 | function next(suite) { |
| 295 | 8 | self.suite = suite; |
| 296 | ||
| 297 | 8 | if (!suite) { |
| 298 | 2 | self.suite = orig; |
| 299 | 2 | return fn(); |
| 300 | } | |
| 301 | ||
| 302 | 6 | self.hook(name, function(err){ |
| 303 | 6 | if (err) { |
| 304 | 0 | var errSuite = self.suite; |
| 305 | 0 | self.suite = orig; |
| 306 | 0 | return fn(err, errSuite); |
| 307 | } | |
| 308 | ||
| 309 | 6 | next(suites.pop()); |
| 310 | }); | |
| 311 | } | |
| 312 | ||
| 313 | 2 | next(suites.pop()); |
| 314 | }; | |
| 315 | ||
| 316 | /** | |
| 317 | * Run hooks from the top level down. | |
| 318 | * | |
| 319 | * @param {String} name | |
| 320 | * @param {Function} fn | |
| 321 | * @api private | |
| 322 | */ | |
| 323 | ||
| 324 | 1 | Runner.prototype.hookUp = function(name, fn){ |
| 325 | 1 | var suites = [this.suite].concat(this.parents()).reverse(); |
| 326 | 1 | this.hooks(name, suites, fn); |
| 327 | }; | |
| 328 | ||
| 329 | /** | |
| 330 | * Run hooks from the bottom up. | |
| 331 | * | |
| 332 | * @param {String} name | |
| 333 | * @param {Function} fn | |
| 334 | * @api private | |
| 335 | */ | |
| 336 | ||
| 337 | 1 | Runner.prototype.hookDown = function(name, fn){ |
| 338 | 1 | var suites = [this.suite].concat(this.parents()); |
| 339 | 1 | this.hooks(name, suites, fn); |
| 340 | }; | |
| 341 | ||
| 342 | /** | |
| 343 | * Return an array of parent Suites from | |
| 344 | * closest to furthest. | |
| 345 | * | |
| 346 | * @return {Array} | |
| 347 | * @api private | |
| 348 | */ | |
| 349 | ||
| 350 | 1 | Runner.prototype.parents = function(){ |
| 351 | 2 | var suite = this.suite |
| 352 | , suites = []; | |
| 353 | 6 | while (suite = suite.parent) suites.push(suite); |
| 354 | 2 | return suites; |
| 355 | }; | |
| 356 | ||
| 357 | /** | |
| 358 | * Run the current test and callback `fn(err)`. | |
| 359 | * | |
| 360 | * @param {Function} fn | |
| 361 | * @api private | |
| 362 | */ | |
| 363 | ||
| 364 | 1 | Runner.prototype.runTest = function(fn){ |
| 365 | 1 | var test = this.test |
| 366 | , self = this; | |
| 367 | ||
| 368 | 1 | if (this.asyncOnly) test.asyncOnly = true; |
| 369 | ||
| 370 | 1 | try { |
| 371 | 1 | test.on('error', function(err){ |
| 372 | 0 | self.fail(test, err); |
| 373 | }); | |
| 374 | 1 | test.run(fn); |
| 375 | } catch (err) { | |
| 376 | 0 | fn(err); |
| 377 | } | |
| 378 | }; | |
| 379 | ||
| 380 | /** | |
| 381 | * Run tests in the given `suite` and invoke | |
| 382 | * the callback `fn()` when complete. | |
| 383 | * | |
| 384 | * @param {Suite} suite | |
| 385 | * @param {Function} fn | |
| 386 | * @api private | |
| 387 | */ | |
| 388 | ||
| 389 | 1 | Runner.prototype.runTests = function(suite, fn){ |
| 390 | 4 | var self = this |
| 391 | , tests = suite.tests.slice() | |
| 392 | , test; | |
| 393 | ||
| 394 | ||
| 395 | 4 | function hookErr(err, errSuite, after) { |
| 396 | // before/after Each hook for errSuite failed: | |
| 397 | 0 | var orig = self.suite; |
| 398 | ||
| 399 | // for failed 'after each' hook start from errSuite parent, | |
| 400 | // otherwise start from errSuite itself | |
| 401 | 0 | self.suite = after ? errSuite.parent : errSuite; |
| 402 | ||
| 403 | 0 | if (self.suite) { |
| 404 | // call hookUp afterEach | |
| 405 | 0 | self.hookUp('afterEach', function(err2, errSuite2) { |
| 406 | 0 | self.suite = orig; |
| 407 | // some hooks may fail even now | |
| 408 | 0 | if (err2) return hookErr(err2, errSuite2, true); |
| 409 | // report error suite | |
| 410 | 0 | fn(errSuite); |
| 411 | }); | |
| 412 | } else { | |
| 413 | // there is no need calling other 'after each' hooks | |
| 414 | 0 | self.suite = orig; |
| 415 | 0 | fn(errSuite); |
| 416 | } | |
| 417 | } | |
| 418 | ||
| 419 | 4 | function next(err, errSuite) { |
| 420 | // if we bail after first err | |
| 421 | 6 | if (self.failures && suite._bail) return fn(); |
| 422 | ||
| 423 | 6 | if (self._abort) return fn(); |
| 424 | ||
| 425 | 6 | if (err) return hookErr(err, errSuite, true); |
| 426 | ||
| 427 | // next test | |
| 428 | 6 | test = tests.shift(); |
| 429 | ||
| 430 | // all done | |
| 431 | 10 | if (!test) return fn(); |
| 432 | ||
| 433 | // grep | |
| 434 | 2 | var match = self._grep.test(test.fullTitle()); |
| 435 | 2 | if (self._invert) match = !match; |
| 436 | 2 | if (!match) return next(); |
| 437 | ||
| 438 | // pending | |
| 439 | 2 | if (test.pending) { |
| 440 | 1 | self.emit('pending', test); |
| 441 | 1 | self.emit('test end', test); |
| 442 | 1 | return next(); |
| 443 | } | |
| 444 | ||
| 445 | // execute test and hook(s) | |
| 446 | 1 | self.emit('test', self.test = test); |
| 447 | 1 | self.hookDown('beforeEach', function(err, errSuite){ |
| 448 | ||
| 449 | 1 | if (err) return hookErr(err, errSuite, false); |
| 450 | ||
| 451 | 1 | self.currentRunnable = self.test; |
| 452 | 1 | self.runTest(function(err){ |
| 453 | 1 | test = self.test; |
| 454 | ||
| 455 | 1 | if (err) { |
| 456 | 0 | self.fail(test, err); |
| 457 | 0 | self.emit('test end', test); |
| 458 | 0 | return self.hookUp('afterEach', next); |
| 459 | } | |
| 460 | ||
| 461 | 1 | test.state = 'passed'; |
| 462 | 1 | self.emit('pass', test); |
| 463 | 1 | self.emit('test end', test); |
| 464 | 1 | self.hookUp('afterEach', next); |
| 465 | }); | |
| 466 | }); | |
| 467 | } | |
| 468 | ||
| 469 | 4 | this.next = next; |
| 470 | 4 | next(); |
| 471 | }; | |
| 472 | ||
| 473 | /** | |
| 474 | * Run the given `suite` and invoke the | |
| 475 | * callback `fn()` when complete. | |
| 476 | * | |
| 477 | * @param {Suite} suite | |
| 478 | * @param {Function} fn | |
| 479 | * @api private | |
| 480 | */ | |
| 481 | ||
| 482 | 1 | Runner.prototype.runSuite = function(suite, fn){ |
| 483 | 5 | var total = this.grepTotal(suite) |
| 484 | , self = this | |
| 485 | , i = 0; | |
| 486 | ||
| 487 | 5 | debug('run suite %s', suite.fullTitle()); |
| 488 | ||
| 489 | 6 | if (!total) return fn(); |
| 490 | ||
| 491 | 4 | this.emit('suite', this.suite = suite); |
| 492 | ||
| 493 | 4 | function next(errSuite) { |
| 494 | 7 | if (errSuite) { |
| 495 | // current suite failed on a hook from errSuite | |
| 496 | 0 | if (errSuite == suite) { |
| 497 | // if errSuite is current suite | |
| 498 | // continue to the next sibling suite | |
| 499 | 0 | return done(); |
| 500 | } else { | |
| 501 | // errSuite is among the parents of current suite | |
| 502 | // stop execution of errSuite and all sub-suites | |
| 503 | 0 | return done(errSuite); |
| 504 | } | |
| 505 | } | |
| 506 | ||
| 507 | 7 | if (self._abort) return done(); |
| 508 | ||
| 509 | 7 | var curr = suite.suites[i++]; |
| 510 | 11 | if (!curr) return done(); |
| 511 | 3 | self.runSuite(curr, next); |
| 512 | } | |
| 513 | ||
| 514 | 4 | function done(errSuite) { |
| 515 | 4 | self.suite = suite; |
| 516 | 4 | self.hook('afterAll', function(){ |
| 517 | 4 | self.emit('suite end', suite); |
| 518 | 4 | fn(errSuite); |
| 519 | }); | |
| 520 | } | |
| 521 | ||
| 522 | 4 | this.hook('beforeAll', function(err){ |
| 523 | 4 | if (err) return done(); |
| 524 | 4 | self.runTests(suite, next); |
| 525 | }); | |
| 526 | }; | |
| 527 | ||
| 528 | /** | |
| 529 | * Handle uncaught exceptions. | |
| 530 | * | |
| 531 | * @param {Error} err | |
| 532 | * @api private | |
| 533 | */ | |
| 534 | ||
| 535 | 1 | Runner.prototype.uncaught = function(err){ |
| 536 | 0 | debug('uncaught exception %s', err.message); |
| 537 | 0 | var runnable = this.currentRunnable; |
| 538 | 0 | if (!runnable || 'failed' == runnable.state) return; |
| 539 | 0 | runnable.clearTimeout(); |
| 540 | 0 | err.uncaught = true; |
| 541 | 0 | this.fail(runnable, err); |
| 542 | ||
| 543 | // recover from test | |
| 544 | 0 | if ('test' == runnable.type) { |
| 545 | 0 | this.emit('test end', runnable); |
| 546 | 0 | this.hookUp('afterEach', this.next); |
| 547 | 0 | return; |
| 548 | } | |
| 549 | ||
| 550 | // bail on hooks | |
| 551 | 0 | this.emit('end'); |
| 552 | }; | |
| 553 | ||
| 554 | /** | |
| 555 | * Run the root suite and invoke `fn(failures)` | |
| 556 | * on completion. | |
| 557 | * | |
| 558 | * @param {Function} fn | |
| 559 | * @return {Runner} for chaining | |
| 560 | * @api public | |
| 561 | */ | |
| 562 | ||
| 563 | 1 | Runner.prototype.run = function(fn){ |
| 564 | 2 | var self = this |
| 565 | , fn = fn || function(){}; | |
| 566 | ||
| 567 | 2 | function uncaught(err){ |
| 568 | 0 | self.uncaught(err); |
| 569 | } | |
| 570 | ||
| 571 | 2 | debug('start'); |
| 572 | ||
| 573 | // callback | |
| 574 | 2 | this.on('end', function(){ |
| 575 | 1 | debug('end'); |
| 576 | 1 | process.removeListener('uncaughtException', uncaught); |
| 577 | 1 | fn(self.failures); |
| 578 | }); | |
| 579 | ||
| 580 | // run suites | |
| 581 | 2 | this.emit('start'); |
| 582 | 2 | this.runSuite(this.suite, function(){ |
| 583 | 2 | debug('finished running'); |
| 584 | 2 | self.emit('end'); |
| 585 | }); | |
| 586 | ||
| 587 | // uncaught exception | |
| 588 | 1 | process.on('uncaughtException', uncaught); |
| 589 | ||
| 590 | 1 | return this; |
| 591 | }; | |
| 592 | ||
| 593 | /** | |
| 594 | * Cleanly abort execution | |
| 595 | * | |
| 596 | * @return {Runner} for chaining | |
| 597 | * @api public | |
| 598 | */ | |
| 599 | 1 | Runner.prototype.abort = function(){ |
| 600 | 0 | debug('aborting'); |
| 601 | 0 | this._abort = true; |
| 602 | } | |
| 603 | ||
| 604 | /** | |
| 605 | * Filter leaks with the given globals flagged as `ok`. | |
| 606 | * | |
| 607 | * @param {Array} ok | |
| 608 | * @param {Array} globals | |
| 609 | * @return {Array} | |
| 610 | * @api private | |
| 611 | */ | |
| 612 | ||
| 613 | 1 | function filterLeaks(ok, globals) { |
| 614 | 1 | return filter(globals, function(key){ |
| 615 | // Firefox and Chrome exposes iframes as index inside the window object | |
| 616 | 51 | if (/^d+/.test(key)) return false; |
| 617 | ||
| 618 | // in firefox | |
| 619 | // if runner runs in an iframe, this iframe's window.getInterface method not init at first | |
| 620 | // it is assigned in some seconds | |
| 621 | 49 | if (global.navigator && /^getInterface/.test(key)) return false; |
| 622 | ||
| 623 | // an iframe could be approached by window[iframeIndex] | |
| 624 | // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak | |
| 625 | 49 | if (global.navigator && /^\d+/.test(key)) return false; |
| 626 | ||
| 627 | // Opera and IE expose global variables for HTML element IDs (issue #243) | |
| 628 | 49 | if (/^mocha-/.test(key)) return false; |
| 629 | ||
| 630 | 49 | var matched = filter(ok, function(ok){ |
| 631 | 2499 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); |
| 632 | 2499 | return key == ok; |
| 633 | }); | |
| 634 | 49 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); |
| 635 | }); | |
| 636 | } | |
| 637 | ||
| 638 | /** | |
| 639 | * Array of globals dependent on the environment. | |
| 640 | * | |
| 641 | * @return {Array} | |
| 642 | * @api private | |
| 643 | */ | |
| 644 | ||
| 645 | 1 | function extraGlobals() { |
| 646 | 2 | if (typeof(process) === 'object' && |
| 647 | typeof(process.version) === 'string') { | |
| 648 | ||
| 649 | 2 | var nodeVersion = process.version.split('.').reduce(function(a, v) { |
| 650 | 4 | return a << 8 | v; |
| 651 | }); | |
| 652 | ||
| 653 | // 'errno' was renamed to process._errno in v0.9.11. | |
| 654 | ||
| 655 | 2 | if (nodeVersion < 0x00090B) { |
| 656 | 0 | return ['errno']; |
| 657 | } | |
| 658 | } | |
| 659 | ||
| 660 | 2 | return []; |
| 661 | } | |
| 662 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var EventEmitter = require('events').EventEmitter |
| 7 | , debug = require('debug')('mocha:suite') | |
| 8 | , milliseconds = require('./ms') | |
| 9 | , utils = require('./utils') | |
| 10 | , Hook = require('./hook'); | |
| 11 | ||
| 12 | /** | |
| 13 | * Expose `Suite`. | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | exports = module.exports = Suite; |
| 17 | ||
| 18 | /** | |
| 19 | * Create a new `Suite` with the given `title` | |
| 20 | * and parent `Suite`. When a suite with the | |
| 21 | * same title is already present, that suite | |
| 22 | * is returned to provide nicer reporter | |
| 23 | * and more flexible meta-testing. | |
| 24 | * | |
| 25 | * @param {Suite} parent | |
| 26 | * @param {String} title | |
| 27 | * @return {Suite} | |
| 28 | * @api public | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | exports.create = function(parent, title){ |
| 32 | 3 | var suite = new Suite(title, parent.ctx); |
| 33 | 3 | suite.parent = parent; |
| 34 | 3 | if (parent.pending) suite.pending = true; |
| 35 | 3 | title = suite.fullTitle(); |
| 36 | 3 | parent.addSuite(suite); |
| 37 | 3 | return suite; |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * Initialize a new `Suite` with the given | |
| 42 | * `title` and `ctx`. | |
| 43 | * | |
| 44 | * @param {String} title | |
| 45 | * @param {Context} ctx | |
| 46 | * @api private | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | function Suite(title, ctx) { |
| 50 | 5 | this.title = title; |
| 51 | 5 | this.ctx = ctx; |
| 52 | 5 | this.suites = []; |
| 53 | 5 | this.tests = []; |
| 54 | 5 | this.pending = false; |
| 55 | 5 | this._beforeEach = []; |
| 56 | 5 | this._beforeAll = []; |
| 57 | 5 | this._afterEach = []; |
| 58 | 5 | this._afterAll = []; |
| 59 | 5 | this.root = !title; |
| 60 | 5 | this._timeout = 2000; |
| 61 | 5 | this._slow = 75; |
| 62 | 5 | this._bail = false; |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Inherit from `EventEmitter.prototype`. | |
| 67 | */ | |
| 68 | ||
| 69 | 1 | Suite.prototype.__proto__ = EventEmitter.prototype; |
| 70 | ||
| 71 | /** | |
| 72 | * Return a clone of this `Suite`. | |
| 73 | * | |
| 74 | * @return {Suite} | |
| 75 | * @api private | |
| 76 | */ | |
| 77 | ||
| 78 | 1 | Suite.prototype.clone = function(){ |
| 79 | 0 | var suite = new Suite(this.title); |
| 80 | 0 | debug('clone'); |
| 81 | 0 | suite.ctx = this.ctx; |
| 82 | 0 | suite.timeout(this.timeout()); |
| 83 | 0 | suite.slow(this.slow()); |
| 84 | 0 | suite.bail(this.bail()); |
| 85 | 0 | return suite; |
| 86 | }; | |
| 87 | ||
| 88 | /** | |
| 89 | * Set timeout `ms` or short-hand such as "2s". | |
| 90 | * | |
| 91 | * @param {Number|String} ms | |
| 92 | * @return {Suite|Number} for chaining | |
| 93 | * @api private | |
| 94 | */ | |
| 95 | ||
| 96 | 1 | Suite.prototype.timeout = function(ms){ |
| 97 | 18 | if (0 == arguments.length) return this._timeout; |
| 98 | 4 | if ('string' == typeof ms) ms = milliseconds(ms); |
| 99 | 4 | debug('timeout %d', ms); |
| 100 | 4 | this._timeout = parseInt(ms, 10); |
| 101 | 4 | return this; |
| 102 | }; | |
| 103 | ||
| 104 | /** | |
| 105 | * Set slow `ms` or short-hand such as "2s". | |
| 106 | * | |
| 107 | * @param {Number|String} ms | |
| 108 | * @return {Suite|Number} for chaining | |
| 109 | * @api private | |
| 110 | */ | |
| 111 | ||
| 112 | 1 | Suite.prototype.slow = function(ms){ |
| 113 | 17 | if (0 === arguments.length) return this._slow; |
| 114 | 3 | if ('string' == typeof ms) ms = milliseconds(ms); |
| 115 | 3 | debug('slow %d', ms); |
| 116 | 3 | this._slow = ms; |
| 117 | 3 | return this; |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Sets whether to bail after first error. | |
| 122 | * | |
| 123 | * @parma {Boolean} bail | |
| 124 | * @return {Suite|Number} for chaining | |
| 125 | * @api private | |
| 126 | */ | |
| 127 | ||
| 128 | 1 | Suite.prototype.bail = function(bail){ |
| 129 | 11 | if (0 == arguments.length) return this._bail; |
| 130 | 5 | debug('bail %s', bail); |
| 131 | 5 | this._bail = bail; |
| 132 | 5 | return this; |
| 133 | }; | |
| 134 | ||
| 135 | /** | |
| 136 | * Run `fn(test[, done])` before running tests. | |
| 137 | * | |
| 138 | * @param {Function} fn | |
| 139 | * @return {Suite} for chaining | |
| 140 | * @api private | |
| 141 | */ | |
| 142 | ||
| 143 | 1 | Suite.prototype.beforeAll = function(fn){ |
| 144 | 0 | if (this.pending) return this; |
| 145 | 0 | var hook = new Hook('"before all" hook', fn); |
| 146 | 0 | hook.parent = this; |
| 147 | 0 | hook.timeout(this.timeout()); |
| 148 | 0 | hook.slow(this.slow()); |
| 149 | 0 | hook.ctx = this.ctx; |
| 150 | 0 | this._beforeAll.push(hook); |
| 151 | 0 | this.emit('beforeAll', hook); |
| 152 | 0 | return this; |
| 153 | }; | |
| 154 | ||
| 155 | /** | |
| 156 | * Run `fn(test[, done])` after running tests. | |
| 157 | * | |
| 158 | * @param {Function} fn | |
| 159 | * @return {Suite} for chaining | |
| 160 | * @api private | |
| 161 | */ | |
| 162 | ||
| 163 | 1 | Suite.prototype.afterAll = function(fn){ |
| 164 | 0 | if (this.pending) return this; |
| 165 | 0 | var hook = new Hook('"after all" hook', fn); |
| 166 | 0 | hook.parent = this; |
| 167 | 0 | hook.timeout(this.timeout()); |
| 168 | 0 | hook.slow(this.slow()); |
| 169 | 0 | hook.ctx = this.ctx; |
| 170 | 0 | this._afterAll.push(hook); |
| 171 | 0 | this.emit('afterAll', hook); |
| 172 | 0 | return this; |
| 173 | }; | |
| 174 | ||
| 175 | /** | |
| 176 | * Run `fn(test[, done])` before each test case. | |
| 177 | * | |
| 178 | * @param {Function} fn | |
| 179 | * @return {Suite} for chaining | |
| 180 | * @api private | |
| 181 | */ | |
| 182 | ||
| 183 | 1 | Suite.prototype.beforeEach = function(fn){ |
| 184 | 4 | if (this.pending) return this; |
| 185 | 2 | var hook = new Hook('"before each" hook', fn); |
| 186 | 2 | hook.parent = this; |
| 187 | 2 | hook.timeout(this.timeout()); |
| 188 | 2 | hook.slow(this.slow()); |
| 189 | 2 | hook.ctx = this.ctx; |
| 190 | 2 | this._beforeEach.push(hook); |
| 191 | 2 | this.emit('beforeEach', hook); |
| 192 | 2 | return this; |
| 193 | }; | |
| 194 | ||
| 195 | /** | |
| 196 | * Run `fn(test[, done])` after each test case. | |
| 197 | * | |
| 198 | * @param {Function} fn | |
| 199 | * @return {Suite} for chaining | |
| 200 | * @api private | |
| 201 | */ | |
| 202 | ||
| 203 | 1 | Suite.prototype.afterEach = function(fn){ |
| 204 | 2 | if (this.pending) return this; |
| 205 | 0 | var hook = new Hook('"after each" hook', fn); |
| 206 | 0 | hook.parent = this; |
| 207 | 0 | hook.timeout(this.timeout()); |
| 208 | 0 | hook.slow(this.slow()); |
| 209 | 0 | hook.ctx = this.ctx; |
| 210 | 0 | this._afterEach.push(hook); |
| 211 | 0 | this.emit('afterEach', hook); |
| 212 | 0 | return this; |
| 213 | }; | |
| 214 | ||
| 215 | /** | |
| 216 | * Add a test `suite`. | |
| 217 | * | |
| 218 | * @param {Suite} suite | |
| 219 | * @return {Suite} for chaining | |
| 220 | * @api private | |
| 221 | */ | |
| 222 | ||
| 223 | 1 | Suite.prototype.addSuite = function(suite){ |
| 224 | 3 | suite.parent = this; |
| 225 | 3 | suite.timeout(this.timeout()); |
| 226 | 3 | suite.slow(this.slow()); |
| 227 | 3 | suite.bail(this.bail()); |
| 228 | 3 | this.suites.push(suite); |
| 229 | 3 | this.emit('suite', suite); |
| 230 | 3 | return this; |
| 231 | }; | |
| 232 | ||
| 233 | /** | |
| 234 | * Add a `test` to this suite. | |
| 235 | * | |
| 236 | * @param {Test} test | |
| 237 | * @return {Suite} for chaining | |
| 238 | * @api private | |
| 239 | */ | |
| 240 | ||
| 241 | 1 | Suite.prototype.addTest = function(test){ |
| 242 | 2 | test.parent = this; |
| 243 | 2 | test.timeout(this.timeout()); |
| 244 | 2 | test.slow(this.slow()); |
| 245 | 2 | test.ctx = this.ctx; |
| 246 | 2 | this.tests.push(test); |
| 247 | 2 | this.emit('test', test); |
| 248 | 2 | return this; |
| 249 | }; | |
| 250 | ||
| 251 | /** | |
| 252 | * Return the full title generated by recursively | |
| 253 | * concatenating the parent's full title. | |
| 254 | * | |
| 255 | * @return {String} | |
| 256 | * @api public | |
| 257 | */ | |
| 258 | ||
| 259 | 1 | Suite.prototype.fullTitle = function(){ |
| 260 | 39 | if (this.parent) { |
| 261 | 22 | var full = this.parent.fullTitle(); |
| 262 | 29 | if (full) return full + ' ' + this.title; |
| 263 | } | |
| 264 | 32 | return this.title; |
| 265 | }; | |
| 266 | ||
| 267 | /** | |
| 268 | * Return the total number of tests. | |
| 269 | * | |
| 270 | * @return {Number} | |
| 271 | * @api public | |
| 272 | */ | |
| 273 | ||
| 274 | 1 | Suite.prototype.total = function(){ |
| 275 | 5 | return utils.reduce(this.suites, function(sum, suite){ |
| 276 | 3 | return sum + suite.total(); |
| 277 | }, 0) + this.tests.length; | |
| 278 | }; | |
| 279 | ||
| 280 | /** | |
| 281 | * Iterates through each suite recursively to find | |
| 282 | * all tests. Applies a function in the format | |
| 283 | * `fn(test)`. | |
| 284 | * | |
| 285 | * @param {Function} fn | |
| 286 | * @return {Suite} | |
| 287 | * @api private | |
| 288 | */ | |
| 289 | ||
| 290 | 1 | Suite.prototype.eachTest = function(fn){ |
| 291 | 14 | utils.forEach(this.tests, fn); |
| 292 | 14 | utils.forEach(this.suites, function(suite){ |
| 293 | 7 | suite.eachTest(fn); |
| 294 | }); | |
| 295 | 14 | return this; |
| 296 | }; | |
| 297 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Runnable = require('./runnable'); |
| 7 | ||
| 8 | /** | |
| 9 | * Expose `Test`. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | module.exports = Test; |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Test` with the given `title` and callback `fn`. | |
| 16 | * | |
| 17 | * @param {String} title | |
| 18 | * @param {Function} fn | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function Test(title, fn) { |
| 23 | 2 | Runnable.call(this, title, fn); |
| 24 | 2 | this.pending = !fn; |
| 25 | 2 | this.type = 'test'; |
| 26 | } | |
| 27 | ||
| 28 | /** | |
| 29 | * Inherit from `Runnable.prototype`. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | Test.prototype.__proto__ = Runnable.prototype; |
| 33 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var fs = require('fs') |
| 6 | , path = require('path') | |
| 7 | , join = path.join | |
| 8 | , debug = require('debug')('mocha:watch'); | |
| 9 | ||
| 10 | /** | |
| 11 | * Ignored directories. | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | var ignore = ['node_modules', '.git']; |
| 15 | ||
| 16 | /** | |
| 17 | * Escape special characters in the given string of html. | |
| 18 | * | |
| 19 | * @param {String} html | |
| 20 | * @return {String} | |
| 21 | * @api private | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | exports.escape = function(html){ |
| 25 | 0 | return String(html) |
| 26 | .replace(/&/g, '&') | |
| 27 | .replace(/"/g, '"') | |
| 28 | .replace(/</g, '<') | |
| 29 | .replace(/>/g, '>'); | |
| 30 | }; | |
| 31 | ||
| 32 | /** | |
| 33 | * Array#forEach (<=IE8) | |
| 34 | * | |
| 35 | * @param {Array} array | |
| 36 | * @param {Function} fn | |
| 37 | * @param {Object} scope | |
| 38 | * @api private | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | exports.forEach = function(arr, fn, scope){ |
| 42 | 28 | for (var i = 0, l = arr.length; i < l; i++) |
| 43 | 14 | fn.call(scope, arr[i], i); |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Array#map (<=IE8) | |
| 48 | * | |
| 49 | * @param {Array} array | |
| 50 | * @param {Function} fn | |
| 51 | * @param {Object} scope | |
| 52 | * @api private | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | exports.map = function(arr, fn, scope){ |
| 56 | 0 | var result = []; |
| 57 | 0 | for (var i = 0, l = arr.length; i < l; i++) |
| 58 | 0 | result.push(fn.call(scope, arr[i], i)); |
| 59 | 0 | return result; |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Array#indexOf (<=IE8) | |
| 64 | * | |
| 65 | * @parma {Array} arr | |
| 66 | * @param {Object} obj to find index of | |
| 67 | * @param {Number} start | |
| 68 | * @api private | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | exports.indexOf = function(arr, obj, start){ |
| 72 | 36 | for (var i = start || 0, l = arr.length; i < l; i++) { |
| 73 | 1218 | if (arr[i] === obj) |
| 74 | 24 | return i; |
| 75 | } | |
| 76 | 12 | return -1; |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Array#reduce (<=IE8) | |
| 81 | * | |
| 82 | * @param {Array} array | |
| 83 | * @param {Function} fn | |
| 84 | * @param {Object} initial value | |
| 85 | * @api private | |
| 86 | */ | |
| 87 | ||
| 88 | 1 | exports.reduce = function(arr, fn, val){ |
| 89 | 5 | var rval = val; |
| 90 | ||
| 91 | 5 | for (var i = 0, l = arr.length; i < l; i++) { |
| 92 | 3 | rval = fn(rval, arr[i], i, arr); |
| 93 | } | |
| 94 | ||
| 95 | 5 | return rval; |
| 96 | }; | |
| 97 | ||
| 98 | /** | |
| 99 | * Array#filter (<=IE8) | |
| 100 | * | |
| 101 | * @param {Array} array | |
| 102 | * @param {Function} fn | |
| 103 | * @api private | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | exports.filter = function(arr, fn){ |
| 107 | 50 | var ret = []; |
| 108 | ||
| 109 | 50 | for (var i = 0, l = arr.length; i < l; i++) { |
| 110 | 2549 | var val = arr[i]; |
| 111 | 2598 | if (fn(val, i, arr)) ret.push(val); |
| 112 | } | |
| 113 | ||
| 114 | 50 | return ret; |
| 115 | }; | |
| 116 | ||
| 117 | /** | |
| 118 | * Object.keys (<=IE8) | |
| 119 | * | |
| 120 | * @param {Object} obj | |
| 121 | * @return {Array} keys | |
| 122 | * @api private | |
| 123 | */ | |
| 124 | ||
| 125 | 1 | exports.keys = Object.keys || function(obj) { |
| 126 | 0 | var keys = [] |
| 127 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 | |
| 128 | ||
| 129 | 0 | for (var key in obj) { |
| 130 | 0 | if (has.call(obj, key)) { |
| 131 | 0 | keys.push(key); |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | 0 | return keys; |
| 136 | }; | |
| 137 | ||
| 138 | /** | |
| 139 | * Watch the given `files` for changes | |
| 140 | * and invoke `fn(file)` on modification. | |
| 141 | * | |
| 142 | * @param {Array} files | |
| 143 | * @param {Function} fn | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | exports.watch = function(files, fn){ |
| 148 | 0 | var options = { interval: 100 }; |
| 149 | 0 | files.forEach(function(file){ |
| 150 | 0 | debug('file %s', file); |
| 151 | 0 | fs.watchFile(file, options, function(curr, prev){ |
| 152 | 0 | if (prev.mtime < curr.mtime) fn(file); |
| 153 | }); | |
| 154 | }); | |
| 155 | }; | |
| 156 | ||
| 157 | /** | |
| 158 | * Ignored files. | |
| 159 | */ | |
| 160 | ||
| 161 | 1 | function ignored(path){ |
| 162 | 0 | return !~ignore.indexOf(path); |
| 163 | } | |
| 164 | ||
| 165 | /** | |
| 166 | * Lookup files in the given `dir`. | |
| 167 | * | |
| 168 | * @return {Array} | |
| 169 | * @api private | |
| 170 | */ | |
| 171 | ||
| 172 | 1 | exports.files = function(dir, ret){ |
| 173 | 0 | ret = ret || []; |
| 174 | ||
| 175 | 0 | fs.readdirSync(dir) |
| 176 | .filter(ignored) | |
| 177 | .forEach(function(path){ | |
| 178 | 0 | path = join(dir, path); |
| 179 | 0 | if (fs.statSync(path).isDirectory()) { |
| 180 | 0 | exports.files(path, ret); |
| 181 | 0 | } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) { |
| 182 | 0 | ret.push(path); |
| 183 | } | |
| 184 | }); | |
| 185 | ||
| 186 | 0 | return ret; |
| 187 | }; | |
| 188 | ||
| 189 | /** | |
| 190 | * Compute a slug from the given `str`. | |
| 191 | * | |
| 192 | * @param {String} str | |
| 193 | * @return {String} | |
| 194 | * @api private | |
| 195 | */ | |
| 196 | ||
| 197 | 1 | exports.slug = function(str){ |
| 198 | 0 | return str |
| 199 | .toLowerCase() | |
| 200 | .replace(/ +/g, '-') | |
| 201 | .replace(/[^-\w]/g, ''); | |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * Strip the function definition from `str`, | |
| 206 | * and re-indent for pre whitespace. | |
| 207 | */ | |
| 208 | ||
| 209 | 1 | exports.clean = function(str) { |
| 210 | 0 | str = str |
| 211 | .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '') | |
| 212 | .replace(/^function *\(.*\) *{/, '') | |
| 213 | .replace(/\s+\}$/, ''); | |
| 214 | ||
| 215 | 0 | var spaces = str.match(/^\n?( *)/)[1].length |
| 216 | , tabs = str.match(/^\n?(\t*)/)[1].length | |
| 217 | , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm'); | |
| 218 | ||
| 219 | 0 | str = str.replace(re, ''); |
| 220 | ||
| 221 | 0 | return exports.trim(str); |
| 222 | }; | |
| 223 | ||
| 224 | /** | |
| 225 | * Escape regular expression characters in `str`. | |
| 226 | * | |
| 227 | * @param {String} str | |
| 228 | * @return {String} | |
| 229 | * @api private | |
| 230 | */ | |
| 231 | ||
| 232 | 1 | exports.escapeRegexp = function(str){ |
| 233 | 0 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); |
| 234 | }; | |
| 235 | ||
| 236 | /** | |
| 237 | * Trim the given `str`. | |
| 238 | * | |
| 239 | * @param {String} str | |
| 240 | * @return {String} | |
| 241 | * @api private | |
| 242 | */ | |
| 243 | ||
| 244 | 1 | exports.trim = function(str){ |
| 245 | 0 | return str.replace(/^\s+|\s+$/g, ''); |
| 246 | }; | |
| 247 | ||
| 248 | /** | |
| 249 | * Parse the given `qs`. | |
| 250 | * | |
| 251 | * @param {String} qs | |
| 252 | * @return {Object} | |
| 253 | * @api private | |
| 254 | */ | |
| 255 | ||
| 256 | 1 | exports.parseQuery = function(qs){ |
| 257 | 0 | return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ |
| 258 | 0 | var i = pair.indexOf('=') |
| 259 | , key = pair.slice(0, i) | |
| 260 | , val = pair.slice(++i); | |
| 261 | ||
| 262 | 0 | obj[key] = decodeURIComponent(val); |
| 263 | 0 | return obj; |
| 264 | }, {}); | |
| 265 | }; | |
| 266 | ||
| 267 | /** | |
| 268 | * Highlight the given string of `js`. | |
| 269 | * | |
| 270 | * @param {String} js | |
| 271 | * @return {String} | |
| 272 | * @api private | |
| 273 | */ | |
| 274 | ||
| 275 | 1 | function highlight(js) { |
| 276 | 0 | return js |
| 277 | .replace(/</g, '<') | |
| 278 | .replace(/>/g, '>') | |
| 279 | .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') | |
| 280 | .replace(/('.*?')/gm, '<span class="string">$1</span>') | |
| 281 | .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') | |
| 282 | .replace(/(\d+)/gm, '<span class="number">$1</span>') | |
| 283 | .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>') | |
| 284 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>') | |
| 285 | } | |
| 286 | ||
| 287 | /** | |
| 288 | * Highlight the contents of tag `name`. | |
| 289 | * | |
| 290 | * @param {String} name | |
| 291 | * @api private | |
| 292 | */ | |
| 293 | ||
| 294 | 1 | exports.highlightTags = function(name) { |
| 295 | 0 | var code = document.getElementsByTagName(name); |
| 296 | 0 | for (var i = 0, len = code.length; i < len; ++i) { |
| 297 | 0 | code[i].innerHTML = highlight(code[i].innerHTML); |
| 298 | } | |
| 299 | }; | |
| 300 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var tty = require('tty'); |
| 6 | ||
| 7 | /** | |
| 8 | * Expose `debug()` as the module. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = debug; |
| 12 | ||
| 13 | /** | |
| 14 | * Enabled debuggers. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | var names = [] |
| 18 | , skips = []; | |
| 19 | ||
| 20 | 1 | (process.env.DEBUG || '') |
| 21 | .split(/[\s,]+/) | |
| 22 | .forEach(function(name){ | |
| 23 | 1 | name = name.replace('*', '.*?'); |
| 24 | 1 | if (name[0] === '-') { |
| 25 | 0 | skips.push(new RegExp('^' + name.substr(1) + '$')); |
| 26 | } else { | |
| 27 | 1 | names.push(new RegExp('^' + name + '$')); |
| 28 | } | |
| 29 | }); | |
| 30 | ||
| 31 | /** | |
| 32 | * Colors. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | var colors = [6, 2, 3, 4, 5, 1]; |
| 36 | ||
| 37 | /** | |
| 38 | * Previous debug() call. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | var prev = {}; |
| 42 | ||
| 43 | /** | |
| 44 | * Previously assigned color. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | var prevColor = 0; |
| 48 | ||
| 49 | /** | |
| 50 | * Is stdout a TTY? Colored output is disabled when `true`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | var isatty = tty.isatty(2); |
| 54 | ||
| 55 | /** | |
| 56 | * Select a color. | |
| 57 | * | |
| 58 | * @return {Number} | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | function color() { |
| 63 | 0 | return colors[prevColor++ % colors.length]; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Humanize the given `ms`. | |
| 68 | * | |
| 69 | * @param {Number} m | |
| 70 | * @return {String} | |
| 71 | * @api private | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | function humanize(ms) { |
| 75 | 0 | var sec = 1000 |
| 76 | , min = 60 * 1000 | |
| 77 | , hour = 60 * min; | |
| 78 | ||
| 79 | 0 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; |
| 80 | 0 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; |
| 81 | 0 | if (ms >= sec) return (ms / sec | 0) + 's'; |
| 82 | 0 | return ms + 'ms'; |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Create a debugger with the given `name`. | |
| 87 | * | |
| 88 | * @param {String} name | |
| 89 | * @return {Type} | |
| 90 | * @api public | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function debug(name) { |
| 94 | 4 | function disabled(){} |
| 95 | 4 | disabled.enabled = false; |
| 96 | ||
| 97 | 4 | var match = skips.some(function(re){ |
| 98 | 0 | return re.test(name); |
| 99 | }); | |
| 100 | ||
| 101 | 4 | if (match) return disabled; |
| 102 | ||
| 103 | 4 | match = names.some(function(re){ |
| 104 | 4 | return re.test(name); |
| 105 | }); | |
| 106 | ||
| 107 | 8 | if (!match) return disabled; |
| 108 | 0 | var c = color(); |
| 109 | ||
| 110 | 0 | function colored(fmt) { |
| 111 | 0 | fmt = coerce(fmt); |
| 112 | ||
| 113 | 0 | var curr = new Date; |
| 114 | 0 | var ms = curr - (prev[name] || curr); |
| 115 | 0 | prev[name] = curr; |
| 116 | ||
| 117 | 0 | fmt = ' \u001b[9' + c + 'm' + name + ' ' |
| 118 | + '\u001b[3' + c + 'm\u001b[90m' | |
| 119 | + fmt + '\u001b[3' + c + 'm' | |
| 120 | + ' +' + humanize(ms) + '\u001b[0m'; | |
| 121 | ||
| 122 | 0 | console.error.apply(this, arguments); |
| 123 | } | |
| 124 | ||
| 125 | 0 | function plain(fmt) { |
| 126 | 0 | fmt = coerce(fmt); |
| 127 | ||
| 128 | 0 | fmt = new Date().toUTCString() |
| 129 | + ' ' + name + ' ' + fmt; | |
| 130 | 0 | console.error.apply(this, arguments); |
| 131 | } | |
| 132 | ||
| 133 | 0 | colored.enabled = plain.enabled = true; |
| 134 | ||
| 135 | 0 | return isatty || process.env.DEBUG_COLORS |
| 136 | ? colored | |
| 137 | : plain; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Coerce `val`. | |
| 142 | */ | |
| 143 | ||
| 144 | 1 | function coerce(val) { |
| 145 | 0 | if (val instanceof Error) return val.stack || val.message; |
| 146 | 0 | return val; |
| 147 | } | |
| 148 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - Compiler | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var nodes = require('./nodes') |
| 13 | , filters = require('./filters') | |
| 14 | , doctypes = require('./doctypes') | |
| 15 | , selfClosing = require('./self-closing') | |
| 16 | , runtime = require('./runtime') | |
| 17 | , utils = require('./utils'); | |
| 18 | ||
| 19 | // if browser | |
| 20 | // | |
| 21 | // if (!Object.keys) { | |
| 22 | // Object.keys = function(obj){ | |
| 23 | // var arr = []; | |
| 24 | // for (var key in obj) { | |
| 25 | // if (obj.hasOwnProperty(key)) { | |
| 26 | // arr.push(key); | |
| 27 | // } | |
| 28 | // } | |
| 29 | // return arr; | |
| 30 | // } | |
| 31 | // } | |
| 32 | // | |
| 33 | // if (!String.prototype.trimLeft) { | |
| 34 | // String.prototype.trimLeft = function(){ | |
| 35 | // return this.replace(/^\s+/, ''); | |
| 36 | // } | |
| 37 | // } | |
| 38 | // | |
| 39 | // end | |
| 40 | ||
| 41 | ||
| 42 | /** | |
| 43 | * Initialize `Compiler` with the given `node`. | |
| 44 | * | |
| 45 | * @param {Node} node | |
| 46 | * @param {Object} options | |
| 47 | * @api public | |
| 48 | */ | |
| 49 | ||
| 50 | 1 | var Compiler = module.exports = function Compiler(node, options) { |
| 51 | 1 | this.options = options = options || {}; |
| 52 | 1 | this.node = node; |
| 53 | 1 | this.hasCompiledDoctype = false; |
| 54 | 1 | this.hasCompiledTag = false; |
| 55 | 1 | this.pp = options.pretty || false; |
| 56 | 1 | this.debug = false !== options.compileDebug; |
| 57 | 1 | this.indents = 0; |
| 58 | 1 | this.parentIndents = 0; |
| 59 | 1 | if (options.doctype) this.setDoctype(options.doctype); |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Compiler prototype. | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | Compiler.prototype = { |
| 67 | ||
| 68 | /** | |
| 69 | * Compile parse tree to JavaScript. | |
| 70 | * | |
| 71 | * @api public | |
| 72 | */ | |
| 73 | ||
| 74 | compile: function(){ | |
| 75 | 1 | this.buf = ['var interp;']; |
| 76 | 1 | if (this.pp) this.buf.push("var __indent = [];"); |
| 77 | 1 | this.lastBufferedIdx = -1; |
| 78 | 1 | this.visit(this.node); |
| 79 | 1 | return this.buf.join('\n'); |
| 80 | }, | |
| 81 | ||
| 82 | /** | |
| 83 | * Sets the default doctype `name`. Sets terse mode to `true` when | |
| 84 | * html 5 is used, causing self-closing tags to end with ">" vs "/>", | |
| 85 | * and boolean attributes are not mirrored. | |
| 86 | * | |
| 87 | * @param {string} name | |
| 88 | * @api public | |
| 89 | */ | |
| 90 | ||
| 91 | setDoctype: function(name){ | |
| 92 | 1 | var doctype = doctypes[(name || 'default').toLowerCase()]; |
| 93 | 1 | doctype = doctype || '<!DOCTYPE ' + name + '>'; |
| 94 | 1 | this.doctype = doctype; |
| 95 | 1 | this.terse = '5' == name || 'html' == name; |
| 96 | 1 | this.xml = 0 == this.doctype.indexOf('<?xml'); |
| 97 | }, | |
| 98 | ||
| 99 | /** | |
| 100 | * Buffer the given `str` optionally escaped. | |
| 101 | * | |
| 102 | * @param {String} str | |
| 103 | * @param {Boolean} esc | |
| 104 | * @api public | |
| 105 | */ | |
| 106 | ||
| 107 | buffer: function(str, esc){ | |
| 108 | 445 | if (esc) str = utils.escape(str); |
| 109 | ||
| 110 | 375 | if (this.lastBufferedIdx == this.buf.length) { |
| 111 | 140 | this.lastBuffered += str; |
| 112 | 140 | this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');" |
| 113 | } else { | |
| 114 | 235 | this.buf.push("buf.push('" + str + "');"); |
| 115 | 235 | this.lastBuffered = str; |
| 116 | 235 | this.lastBufferedIdx = this.buf.length; |
| 117 | } | |
| 118 | }, | |
| 119 | ||
| 120 | /** | |
| 121 | * Buffer an indent based on the current `indent` | |
| 122 | * property and an additional `offset`. | |
| 123 | * | |
| 124 | * @param {Number} offset | |
| 125 | * @param {Boolean} newline | |
| 126 | * @api public | |
| 127 | */ | |
| 128 | ||
| 129 | prettyIndent: function(offset, newline){ | |
| 130 | 0 | offset = offset || 0; |
| 131 | 0 | newline = newline ? '\\n' : ''; |
| 132 | 0 | this.buffer(newline + Array(this.indents + offset).join(' ')); |
| 133 | 0 | if (this.parentIndents) |
| 134 | 0 | this.buf.push("buf.push.apply(buf, __indent);"); |
| 135 | }, | |
| 136 | ||
| 137 | /** | |
| 138 | * Visit `node`. | |
| 139 | * | |
| 140 | * @param {Node} node | |
| 141 | * @api public | |
| 142 | */ | |
| 143 | ||
| 144 | visit: function(node){ | |
| 145 | 272 | var debug = this.debug; |
| 146 | ||
| 147 | 272 | if (debug) { |
| 148 | 272 | this.buf.push('__jade.unshift({ lineno: ' + node.line |
| 149 | + ', filename: ' + (node.filename | |
| 150 | ? JSON.stringify(node.filename) | |
| 151 | : '__jade[0].filename') | |
| 152 | + ' });'); | |
| 153 | } | |
| 154 | ||
| 155 | // Massive hack to fix our context | |
| 156 | // stack for - else[ if] etc | |
| 157 | 272 | if (false === node.debug && this.debug) { |
| 158 | 8 | this.buf.pop(); |
| 159 | 8 | this.buf.pop(); |
| 160 | } | |
| 161 | ||
| 162 | 272 | this.visitNode(node); |
| 163 | ||
| 164 | 544 | if (debug) this.buf.push('__jade.shift();'); |
| 165 | }, | |
| 166 | ||
| 167 | /** | |
| 168 | * Visit `node`. | |
| 169 | * | |
| 170 | * @param {Node} node | |
| 171 | * @api public | |
| 172 | */ | |
| 173 | ||
| 174 | visitNode: function(node){ | |
| 175 | 272 | var name = node.constructor.name |
| 176 | || node.constructor.toString().match(/function ([^(\s]+)()/)[1]; | |
| 177 | 272 | return this['visit' + name](node); |
| 178 | }, | |
| 179 | ||
| 180 | /** | |
| 181 | * Visit case `node`. | |
| 182 | * | |
| 183 | * @param {Literal} node | |
| 184 | * @api public | |
| 185 | */ | |
| 186 | ||
| 187 | visitCase: function(node){ | |
| 188 | 0 | var _ = this.withinCase; |
| 189 | 0 | this.withinCase = true; |
| 190 | 0 | this.buf.push('switch (' + node.expr + '){'); |
| 191 | 0 | this.visit(node.block); |
| 192 | 0 | this.buf.push('}'); |
| 193 | 0 | this.withinCase = _; |
| 194 | }, | |
| 195 | ||
| 196 | /** | |
| 197 | * Visit when `node`. | |
| 198 | * | |
| 199 | * @param {Literal} node | |
| 200 | * @api public | |
| 201 | */ | |
| 202 | ||
| 203 | visitWhen: function(node){ | |
| 204 | 0 | if ('default' == node.expr) { |
| 205 | 0 | this.buf.push('default:'); |
| 206 | } else { | |
| 207 | 0 | this.buf.push('case ' + node.expr + ':'); |
| 208 | } | |
| 209 | 0 | this.visit(node.block); |
| 210 | 0 | this.buf.push(' break;'); |
| 211 | }, | |
| 212 | ||
| 213 | /** | |
| 214 | * Visit literal `node`. | |
| 215 | * | |
| 216 | * @param {Literal} node | |
| 217 | * @api public | |
| 218 | */ | |
| 219 | ||
| 220 | visitLiteral: function(node){ | |
| 221 | 2 | var str = node.str.replace(/\n/g, '\\\\n'); |
| 222 | 2 | this.buffer(str); |
| 223 | }, | |
| 224 | ||
| 225 | /** | |
| 226 | * Visit all nodes in `block`. | |
| 227 | * | |
| 228 | * @param {Block} block | |
| 229 | * @api public | |
| 230 | */ | |
| 231 | ||
| 232 | visitBlock: function(block){ | |
| 233 | 126 | var len = block.nodes.length |
| 234 | , escape = this.escape | |
| 235 | , pp = this.pp | |
| 236 | ||
| 237 | // Block keyword has a special meaning in mixins | |
| 238 | 126 | if (this.parentIndents && block.mode) { |
| 239 | 0 | if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');") |
| 240 | 0 | this.buf.push('block && block();'); |
| 241 | 0 | if (pp) this.buf.push("__indent.pop();") |
| 242 | 0 | return; |
| 243 | } | |
| 244 | ||
| 245 | // Pretty print multi-line text | |
| 246 | 126 | if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText) |
| 247 | 0 | this.prettyIndent(1, true); |
| 248 | ||
| 249 | 126 | for (var i = 0; i < len; ++i) { |
| 250 | // Pretty print text | |
| 251 | 147 | if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText) |
| 252 | 0 | this.prettyIndent(1, false); |
| 253 | ||
| 254 | 147 | this.visit(block.nodes[i]); |
| 255 | // Multiple text nodes are separated by newlines | |
| 256 | 147 | if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText) |
| 257 | 0 | this.buffer('\\n'); |
| 258 | } | |
| 259 | }, | |
| 260 | ||
| 261 | /** | |
| 262 | * Visit `doctype`. Sets terse mode to `true` when html 5 | |
| 263 | * is used, causing self-closing tags to end with ">" vs "/>", | |
| 264 | * and boolean attributes are not mirrored. | |
| 265 | * | |
| 266 | * @param {Doctype} doctype | |
| 267 | * @api public | |
| 268 | */ | |
| 269 | ||
| 270 | visitDoctype: function(doctype){ | |
| 271 | 1 | if (doctype && (doctype.val || !this.doctype)) { |
| 272 | 1 | this.setDoctype(doctype.val || 'default'); |
| 273 | } | |
| 274 | ||
| 275 | 2 | if (this.doctype) this.buffer(this.doctype); |
| 276 | 1 | this.hasCompiledDoctype = true; |
| 277 | }, | |
| 278 | ||
| 279 | /** | |
| 280 | * Visit `mixin`, generating a function that | |
| 281 | * may be called within the template. | |
| 282 | * | |
| 283 | * @param {Mixin} mixin | |
| 284 | * @api public | |
| 285 | */ | |
| 286 | ||
| 287 | visitMixin: function(mixin){ | |
| 288 | 0 | var name = mixin.name.replace(/-/g, '_') + '_mixin' |
| 289 | , args = mixin.args || '' | |
| 290 | , block = mixin.block | |
| 291 | , attrs = mixin.attrs | |
| 292 | , pp = this.pp; | |
| 293 | ||
| 294 | 0 | if (mixin.call) { |
| 295 | 0 | if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');") |
| 296 | 0 | if (block || attrs.length) { |
| 297 | ||
| 298 | 0 | this.buf.push(name + '.call({'); |
| 299 | ||
| 300 | 0 | if (block) { |
| 301 | 0 | this.buf.push('block: function(){'); |
| 302 | ||
| 303 | // Render block with no indents, dynamically added when rendered | |
| 304 | 0 | this.parentIndents++; |
| 305 | 0 | var _indents = this.indents; |
| 306 | 0 | this.indents = 0; |
| 307 | 0 | this.visit(mixin.block); |
| 308 | 0 | this.indents = _indents; |
| 309 | 0 | this.parentIndents--; |
| 310 | ||
| 311 | 0 | if (attrs.length) { |
| 312 | 0 | this.buf.push('},'); |
| 313 | } else { | |
| 314 | 0 | this.buf.push('}'); |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | 0 | if (attrs.length) { |
| 319 | 0 | var val = this.attrs(attrs); |
| 320 | 0 | if (val.inherits) { |
| 321 | 0 | this.buf.push('attributes: merge({' + val.buf |
| 322 | + '}, attributes), escaped: merge(' + val.escaped + ', escaped, true)'); | |
| 323 | } else { | |
| 324 | 0 | this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped); |
| 325 | } | |
| 326 | } | |
| 327 | ||
| 328 | 0 | if (args) { |
| 329 | 0 | this.buf.push('}, ' + args + ');'); |
| 330 | } else { | |
| 331 | 0 | this.buf.push('});'); |
| 332 | } | |
| 333 | ||
| 334 | } else { | |
| 335 | 0 | this.buf.push(name + '(' + args + ');'); |
| 336 | } | |
| 337 | 0 | if (pp) this.buf.push("__indent.pop();") |
| 338 | } else { | |
| 339 | 0 | this.buf.push('var ' + name + ' = function(' + args + '){'); |
| 340 | 0 | this.buf.push('var block = this.block, attributes = this.attributes || {}, escaped = this.escaped || {};'); |
| 341 | 0 | this.parentIndents++; |
| 342 | 0 | this.visit(block); |
| 343 | 0 | this.parentIndents--; |
| 344 | 0 | this.buf.push('};'); |
| 345 | } | |
| 346 | }, | |
| 347 | ||
| 348 | /** | |
| 349 | * Visit `tag` buffering tag markup, generating | |
| 350 | * attributes, visiting the `tag`'s code and block. | |
| 351 | * | |
| 352 | * @param {Tag} tag | |
| 353 | * @api public | |
| 354 | */ | |
| 355 | ||
| 356 | visitTag: function(tag){ | |
| 357 | 102 | this.indents++; |
| 358 | 102 | var name = tag.name |
| 359 | , pp = this.pp; | |
| 360 | ||
| 361 | 102 | if (tag.buffer) name = "' + (" + name + ") + '"; |
| 362 | ||
| 363 | 102 | if (!this.hasCompiledTag) { |
| 364 | 1 | if (!this.hasCompiledDoctype && 'html' == name) { |
| 365 | 0 | this.visitDoctype(); |
| 366 | } | |
| 367 | 1 | this.hasCompiledTag = true; |
| 368 | } | |
| 369 | ||
| 370 | // pretty print | |
| 371 | 102 | if (pp && !tag.isInline()) |
| 372 | 0 | this.prettyIndent(0, true); |
| 373 | ||
| 374 | 102 | if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) { |
| 375 | 0 | this.buffer('<' + name); |
| 376 | 0 | this.visitAttributes(tag.attrs); |
| 377 | 0 | this.terse |
| 378 | ? this.buffer('>') | |
| 379 | : this.buffer('/>'); | |
| 380 | } else { | |
| 381 | // Optimize attributes buffering | |
| 382 | 102 | if (tag.attrs.length) { |
| 383 | 79 | this.buffer('<' + name); |
| 384 | 158 | if (tag.attrs.length) this.visitAttributes(tag.attrs); |
| 385 | 79 | this.buffer('>'); |
| 386 | } else { | |
| 387 | 23 | this.buffer('<' + name + '>'); |
| 388 | } | |
| 389 | 145 | if (tag.code) this.visitCode(tag.code); |
| 390 | 102 | this.escape = 'pre' == tag.name; |
| 391 | 102 | this.visit(tag.block); |
| 392 | ||
| 393 | // pretty print | |
| 394 | 102 | if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline()) |
| 395 | 0 | this.prettyIndent(0, true); |
| 396 | ||
| 397 | 102 | this.buffer('</' + name + '>'); |
| 398 | } | |
| 399 | 102 | this.indents--; |
| 400 | }, | |
| 401 | ||
| 402 | /** | |
| 403 | * Visit `filter`, throwing when the filter does not exist. | |
| 404 | * | |
| 405 | * @param {Filter} filter | |
| 406 | * @api public | |
| 407 | */ | |
| 408 | ||
| 409 | visitFilter: function(filter){ | |
| 410 | 0 | var fn = filters[filter.name]; |
| 411 | ||
| 412 | // unknown filter | |
| 413 | 0 | if (!fn) { |
| 414 | 0 | if (filter.isASTFilter) { |
| 415 | 0 | throw new Error('unknown ast filter "' + filter.name + ':"'); |
| 416 | } else { | |
| 417 | 0 | throw new Error('unknown filter ":' + filter.name + '"'); |
| 418 | } | |
| 419 | } | |
| 420 | ||
| 421 | 0 | if (filter.isASTFilter) { |
| 422 | 0 | this.buf.push(fn(filter.block, this, filter.attrs)); |
| 423 | } else { | |
| 424 | 0 | var text = filter.block.nodes.map(function(node){ return node.val }).join('\n'); |
| 425 | 0 | filter.attrs = filter.attrs || {}; |
| 426 | 0 | filter.attrs.filename = this.options.filename; |
| 427 | 0 | this.buffer(utils.text(fn(text, filter.attrs))); |
| 428 | } | |
| 429 | }, | |
| 430 | ||
| 431 | /** | |
| 432 | * Visit `text` node. | |
| 433 | * | |
| 434 | * @param {Text} text | |
| 435 | * @api public | |
| 436 | */ | |
| 437 | ||
| 438 | visitText: function(text){ | |
| 439 | 19 | text = utils.text(text.val.replace(/\\/g, '\\\\')); |
| 440 | 19 | if (this.escape) text = escape(text); |
| 441 | 19 | this.buffer(text); |
| 442 | }, | |
| 443 | ||
| 444 | /** | |
| 445 | * Visit a `comment`, only buffering when the buffer flag is set. | |
| 446 | * | |
| 447 | * @param {Comment} comment | |
| 448 | * @api public | |
| 449 | */ | |
| 450 | ||
| 451 | visitComment: function(comment){ | |
| 452 | 0 | if (!comment.buffer) return; |
| 453 | 0 | if (this.pp) this.prettyIndent(1, true); |
| 454 | 0 | this.buffer('<!--' + utils.escape(comment.val) + '-->'); |
| 455 | }, | |
| 456 | ||
| 457 | /** | |
| 458 | * Visit a `BlockComment`. | |
| 459 | * | |
| 460 | * @param {Comment} comment | |
| 461 | * @api public | |
| 462 | */ | |
| 463 | ||
| 464 | visitBlockComment: function(comment){ | |
| 465 | 0 | if (!comment.buffer) return; |
| 466 | 0 | if (0 == comment.val.trim().indexOf('if')) { |
| 467 | 0 | this.buffer('<!--[' + comment.val.trim() + ']>'); |
| 468 | 0 | this.visit(comment.block); |
| 469 | 0 | this.buffer('<![endif]-->'); |
| 470 | } else { | |
| 471 | 0 | this.buffer('<!--' + comment.val); |
| 472 | 0 | this.visit(comment.block); |
| 473 | 0 | this.buffer('-->'); |
| 474 | } | |
| 475 | }, | |
| 476 | ||
| 477 | /** | |
| 478 | * Visit `code`, respecting buffer / escape flags. | |
| 479 | * If the code is followed by a block, wrap it in | |
| 480 | * a self-calling function. | |
| 481 | * | |
| 482 | * @param {Code} code | |
| 483 | * @api public | |
| 484 | */ | |
| 485 | ||
| 486 | visitCode: function(code){ | |
| 487 | // Wrap code blocks with {}. | |
| 488 | // we only wrap unbuffered code blocks ATM | |
| 489 | // since they are usually flow control | |
| 490 | ||
| 491 | // Buffer code | |
| 492 | 61 | if (code.buffer) { |
| 493 | 43 | var val = code.val.trimLeft(); |
| 494 | 43 | this.buf.push('var __val__ = ' + val); |
| 495 | 43 | val = 'null == __val__ ? "" : __val__'; |
| 496 | 86 | if (code.escape) val = 'escape(' + val + ')'; |
| 497 | 43 | this.buf.push("buf.push(" + val + ");"); |
| 498 | } else { | |
| 499 | 18 | this.buf.push(code.val); |
| 500 | } | |
| 501 | ||
| 502 | // Block support | |
| 503 | 61 | if (code.block) { |
| 504 | 28 | if (!code.buffer) this.buf.push('{'); |
| 505 | 14 | this.visit(code.block); |
| 506 | 28 | if (!code.buffer) this.buf.push('}'); |
| 507 | } | |
| 508 | }, | |
| 509 | ||
| 510 | /** | |
| 511 | * Visit `each` block. | |
| 512 | * | |
| 513 | * @param {Each} each | |
| 514 | * @api public | |
| 515 | */ | |
| 516 | ||
| 517 | visitEach: function(each){ | |
| 518 | 4 | this.buf.push('' |
| 519 | + '// iterate ' + each.obj + '\n' | |
| 520 | + ';(function(){\n' | |
| 521 | + ' if (\'number\' == typeof ' + each.obj + '.length) {\n' | |
| 522 | + ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' | |
| 523 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); | |
| 524 | ||
| 525 | 4 | this.visit(each.block); |
| 526 | ||
| 527 | 4 | this.buf.push('' |
| 528 | + ' }\n' | |
| 529 | + ' } else {\n' | |
| 530 | + ' for (var ' + each.key + ' in ' + each.obj + ') {\n' | |
| 531 | // if browser | |
| 532 | // + ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){' | |
| 533 | // end | |
| 534 | + ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n'); | |
| 535 | ||
| 536 | 4 | this.visit(each.block); |
| 537 | ||
| 538 | // if browser | |
| 539 | // this.buf.push(' }\n'); | |
| 540 | // end | |
| 541 | ||
| 542 | 4 | this.buf.push(' }\n }\n}).call(this);\n'); |
| 543 | }, | |
| 544 | ||
| 545 | /** | |
| 546 | * Visit `attrs`. | |
| 547 | * | |
| 548 | * @param {Array} attrs | |
| 549 | * @api public | |
| 550 | */ | |
| 551 | ||
| 552 | visitAttributes: function(attrs){ | |
| 553 | 79 | var val = this.attrs(attrs); |
| 554 | 79 | if (val.inherits) { |
| 555 | 0 | this.buf.push("buf.push(attrs(merge({ " + val.buf + |
| 556 | " }, attributes), merge(" + val.escaped + ", escaped, true)));"); | |
| 557 | 79 | } else if (val.constant) { |
| 558 | 70 | eval('var buf={' + val.buf + '};'); |
| 559 | 70 | this.buffer(runtime.attrs(buf, JSON.parse(val.escaped)), true); |
| 560 | } else { | |
| 561 | 9 | this.buf.push("buf.push(attrs({ " + val.buf + " }, " + val.escaped + "));"); |
| 562 | } | |
| 563 | }, | |
| 564 | ||
| 565 | /** | |
| 566 | * Compile attributes. | |
| 567 | */ | |
| 568 | ||
| 569 | attrs: function(attrs){ | |
| 570 | 79 | var buf = [] |
| 571 | , classes = [] | |
| 572 | , escaped = {} | |
| 573 | 85 | , constant = attrs.every(function(attr){ return isConstant(attr.val) }) |
| 574 | , inherits = false; | |
| 575 | ||
| 576 | 158 | if (this.terse) buf.push('terse: true'); |
| 577 | ||
| 578 | 79 | attrs.forEach(function(attr){ |
| 579 | 85 | if (attr.name == 'attributes') return inherits = true; |
| 580 | 85 | escaped[attr.name] = attr.escaped; |
| 581 | 85 | if (attr.name == 'class') { |
| 582 | 69 | classes.push('(' + attr.val + ')'); |
| 583 | } else { | |
| 584 | 16 | var pair = "'" + attr.name + "':(" + attr.val + ')'; |
| 585 | 16 | buf.push(pair); |
| 586 | } | |
| 587 | }); | |
| 588 | ||
| 589 | 79 | if (classes.length) { |
| 590 | 67 | classes = classes.join(" + ' ' + "); |
| 591 | 67 | buf.push("class: " + classes); |
| 592 | } | |
| 593 | ||
| 594 | 79 | return { |
| 595 | buf: buf.join(', ').replace('class:', '"class":'), | |
| 596 | escaped: JSON.stringify(escaped), | |
| 597 | inherits: inherits, | |
| 598 | constant: constant | |
| 599 | }; | |
| 600 | } | |
| 601 | }; | |
| 602 | ||
| 603 | /** | |
| 604 | * Check if expression can be evaluated to a constant | |
| 605 | * | |
| 606 | * @param {String} expression | |
| 607 | * @return {Boolean} | |
| 608 | * @api private | |
| 609 | */ | |
| 610 | ||
| 611 | 1 | function isConstant(val){ |
| 612 | // Check strings/literals | |
| 613 | 85 | if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val)) |
| 614 | 76 | return true; |
| 615 | ||
| 616 | // Check numbers | |
| 617 | 9 | if (!isNaN(Number(val))) |
| 618 | 0 | return true; |
| 619 | ||
| 620 | // Check arrays | |
| 621 | 9 | var matches; |
| 622 | 9 | if (matches = /^ *\[(.*)\] *$/.exec(val)) |
| 623 | 0 | return matches[1].split(',').every(isConstant); |
| 624 | ||
| 625 | 9 | return false; |
| 626 | } | |
| 627 | ||
| 628 | /** | |
| 629 | * Escape the given string of `html`. | |
| 630 | * | |
| 631 | * @param {String} html | |
| 632 | * @return {String} | |
| 633 | * @api private | |
| 634 | */ | |
| 635 | ||
| 636 | 1 | function escape(html){ |
| 637 | 0 | return String(html) |
| 638 | .replace(/&(?!\w+;)/g, '&') | |
| 639 | .replace(/</g, '<') | |
| 640 | .replace(/>/g, '>') | |
| 641 | .replace(/"/g, '"'); | |
| 642 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - doctypes | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | module.exports = { |
| 9 | '5': '<!DOCTYPE html>' | |
| 10 | , 'default': '<!DOCTYPE html>' | |
| 11 | , 'xml': '<?xml version="1.0" encoding="utf-8" ?>' | |
| 12 | , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">' | |
| 13 | , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">' | |
| 14 | , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">' | |
| 15 | , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">' | |
| 16 | , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">' | |
| 17 | , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">' | |
| 18 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - filters | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | module.exports = { |
| 9 | ||
| 10 | /** | |
| 11 | * Wrap text with CDATA block. | |
| 12 | */ | |
| 13 | ||
| 14 | cdata: function(str){ | |
| 15 | 0 | return '<![CDATA[\\n' + str + '\\n]]>'; |
| 16 | }, | |
| 17 | ||
| 18 | /** | |
| 19 | * Transform sass to css, wrapped in style tags. | |
| 20 | */ | |
| 21 | ||
| 22 | sass: function(str){ | |
| 23 | 0 | str = str.replace(/\\n/g, '\n'); |
| 24 | 0 | var sass = require('sass').render(str).replace(/\n/g, '\\n'); |
| 25 | 0 | return '<style type="text/css">' + sass + '</style>'; |
| 26 | }, | |
| 27 | ||
| 28 | /** | |
| 29 | * Transform stylus to css, wrapped in style tags. | |
| 30 | */ | |
| 31 | ||
| 32 | stylus: function(str, options){ | |
| 33 | 0 | var ret; |
| 34 | 0 | str = str.replace(/\\n/g, '\n'); |
| 35 | 0 | var stylus = require('stylus'); |
| 36 | 0 | stylus(str, options).render(function(err, css){ |
| 37 | 0 | if (err) throw err; |
| 38 | 0 | ret = css.replace(/\n/g, '\\n'); |
| 39 | }); | |
| 40 | 0 | return '<style type="text/css">' + ret + '</style>'; |
| 41 | }, | |
| 42 | ||
| 43 | /** | |
| 44 | * Transform less to css, wrapped in style tags. | |
| 45 | */ | |
| 46 | ||
| 47 | less: function(str){ | |
| 48 | 0 | var ret; |
| 49 | 0 | str = str.replace(/\\n/g, '\n'); |
| 50 | 0 | require('less').render(str, function(err, css){ |
| 51 | 0 | if (err) throw err; |
| 52 | 0 | ret = '<style type="text/css">' + css.replace(/\n/g, '\\n') + '</style>'; |
| 53 | }); | |
| 54 | 0 | return ret; |
| 55 | }, | |
| 56 | ||
| 57 | /** | |
| 58 | * Transform markdown to html. | |
| 59 | */ | |
| 60 | ||
| 61 | markdown: function(str){ | |
| 62 | 0 | var md; |
| 63 | ||
| 64 | // support markdown / discount | |
| 65 | 0 | try { |
| 66 | 0 | md = require('markdown'); |
| 67 | } catch (err){ | |
| 68 | 0 | try { |
| 69 | 0 | md = require('discount'); |
| 70 | } catch (err) { | |
| 71 | 0 | try { |
| 72 | 0 | md = require('markdown-js'); |
| 73 | } catch (err) { | |
| 74 | 0 | try { |
| 75 | 0 | md = require('marked'); |
| 76 | } catch (err) { | |
| 77 | 0 | throw new |
| 78 | Error('Cannot find markdown library, install markdown, discount, or marked.'); | |
| 79 | } | |
| 80 | } | |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | 0 | str = str.replace(/\\n/g, '\n'); |
| 85 | 0 | return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'''); |
| 86 | }, | |
| 87 | ||
| 88 | /** | |
| 89 | * Transform coffeescript to javascript. | |
| 90 | */ | |
| 91 | ||
| 92 | coffeescript: function(str){ | |
| 93 | 0 | str = str.replace(/\\n/g, '\n'); |
| 94 | 0 | var js = require('coffee-script').compile(str).replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); |
| 95 | 0 | return '<script type="text/javascript">\\n' + js + '</script>'; |
| 96 | } | |
| 97 | }; | |
| 98 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - inline tags | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | module.exports = [ |
| 9 | 'a' | |
| 10 | , 'abbr' | |
| 11 | , 'acronym' | |
| 12 | , 'b' | |
| 13 | , 'br' | |
| 14 | , 'code' | |
| 15 | , 'em' | |
| 16 | , 'font' | |
| 17 | , 'i' | |
| 18 | , 'img' | |
| 19 | , 'ins' | |
| 20 | , 'kbd' | |
| 21 | , 'map' | |
| 22 | , 'samp' | |
| 23 | , 'small' | |
| 24 | , 'span' | |
| 25 | , 'strong' | |
| 26 | , 'sub' | |
| 27 | , 'sup' | |
| 28 | ]; |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Jade | |
| 3 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 4 | * MIT Licensed | |
| 5 | */ | |
| 6 | ||
| 7 | /** | |
| 8 | * Module dependencies. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | var Parser = require('./parser') |
| 12 | , Lexer = require('./lexer') | |
| 13 | , Compiler = require('./compiler') | |
| 14 | , runtime = require('./runtime') | |
| 15 | // if node | |
| 16 | , fs = require('fs'); | |
| 17 | // end | |
| 18 | ||
| 19 | /** | |
| 20 | * Library version. | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | exports.version = '0.26.3'; |
| 24 | ||
| 25 | /** | |
| 26 | * Expose self closing tags. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | exports.selfClosing = require('./self-closing'); |
| 30 | ||
| 31 | /** | |
| 32 | * Default supported doctypes. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | exports.doctypes = require('./doctypes'); |
| 36 | ||
| 37 | /** | |
| 38 | * Text filters. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | exports.filters = require('./filters'); |
| 42 | ||
| 43 | /** | |
| 44 | * Utilities. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | exports.utils = require('./utils'); |
| 48 | ||
| 49 | /** | |
| 50 | * Expose `Compiler`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | exports.Compiler = Compiler; |
| 54 | ||
| 55 | /** | |
| 56 | * Expose `Parser`. | |
| 57 | */ | |
| 58 | ||
| 59 | 1 | exports.Parser = Parser; |
| 60 | ||
| 61 | /** | |
| 62 | * Expose `Lexer`. | |
| 63 | */ | |
| 64 | ||
| 65 | 1 | exports.Lexer = Lexer; |
| 66 | ||
| 67 | /** | |
| 68 | * Nodes. | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | exports.nodes = require('./nodes'); |
| 72 | ||
| 73 | /** | |
| 74 | * Jade runtime helpers. | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | exports.runtime = runtime; |
| 78 | ||
| 79 | /** | |
| 80 | * Template function cache. | |
| 81 | */ | |
| 82 | ||
| 83 | 1 | exports.cache = {}; |
| 84 | ||
| 85 | /** | |
| 86 | * Parse the given `str` of jade and return a function body. | |
| 87 | * | |
| 88 | * @param {String} str | |
| 89 | * @param {Object} options | |
| 90 | * @return {String} | |
| 91 | * @api private | |
| 92 | */ | |
| 93 | ||
| 94 | 1 | function parse(str, options){ |
| 95 | 1 | try { |
| 96 | // Parse | |
| 97 | 1 | var parser = new Parser(str, options.filename, options); |
| 98 | ||
| 99 | // Compile | |
| 100 | 1 | var compiler = new (options.compiler || Compiler)(parser.parse(), options) |
| 101 | , js = compiler.compile(); | |
| 102 | ||
| 103 | // Debug compiler | |
| 104 | 1 | if (options.debug) { |
| 105 | 0 | console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' ')); |
| 106 | } | |
| 107 | ||
| 108 | 1 | return '' |
| 109 | + 'var buf = [];\n' | |
| 110 | + (options.self | |
| 111 | ? 'var self = locals || {};\n' + js | |
| 112 | : 'with (locals || {}) {\n' + js + '\n}\n') | |
| 113 | + 'return buf.join("");'; | |
| 114 | } catch (err) { | |
| 115 | 0 | parser = parser.context(); |
| 116 | 0 | runtime.rethrow(err, parser.filename, parser.lexer.lineno); |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | /** | |
| 121 | * Compile a `Function` representation of the given jade `str`. | |
| 122 | * | |
| 123 | * Options: | |
| 124 | * | |
| 125 | * - `compileDebug` when `false` debugging code is stripped from the compiled template | |
| 126 | * - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()` | |
| 127 | * for use with the Jade client-side runtime.js | |
| 128 | * | |
| 129 | * @param {String} str | |
| 130 | * @param {Options} options | |
| 131 | * @return {Function} | |
| 132 | * @api public | |
| 133 | */ | |
| 134 | ||
| 135 | 1 | exports.compile = function(str, options){ |
| 136 | 1 | var options = options || {} |
| 137 | , client = options.client | |
| 138 | , filename = options.filename | |
| 139 | ? JSON.stringify(options.filename) | |
| 140 | : 'undefined' | |
| 141 | , fn; | |
| 142 | ||
| 143 | 1 | if (options.compileDebug !== false) { |
| 144 | 1 | fn = [ |
| 145 | 'var __jade = [{ lineno: 1, filename: ' + filename + ' }];' | |
| 146 | , 'try {' | |
| 147 | , parse(String(str), options) | |
| 148 | , '} catch (err) {' | |
| 149 | , ' rethrow(err, __jade[0].filename, __jade[0].lineno);' | |
| 150 | , '}' | |
| 151 | ].join('\n'); | |
| 152 | } else { | |
| 153 | 0 | fn = parse(String(str), options); |
| 154 | } | |
| 155 | ||
| 156 | 1 | if (client) { |
| 157 | 0 | fn = 'attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;\n' + fn; |
| 158 | } | |
| 159 | ||
| 160 | 1 | fn = new Function('locals, attrs, escape, rethrow, merge', fn); |
| 161 | ||
| 162 | 1 | if (client) return fn; |
| 163 | ||
| 164 | 1 | return function(locals){ |
| 165 | 0 | return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow, runtime.merge); |
| 166 | }; | |
| 167 | }; | |
| 168 | ||
| 169 | /** | |
| 170 | * Render the given `str` of jade and invoke | |
| 171 | * the callback `fn(err, str)`. | |
| 172 | * | |
| 173 | * Options: | |
| 174 | * | |
| 175 | * - `cache` enable template caching | |
| 176 | * - `filename` filename required for `include` / `extends` and caching | |
| 177 | * | |
| 178 | * @param {String} str | |
| 179 | * @param {Object|Function} options or fn | |
| 180 | * @param {Function} fn | |
| 181 | * @api public | |
| 182 | */ | |
| 183 | ||
| 184 | 1 | exports.render = function(str, options, fn){ |
| 185 | // swap args | |
| 186 | 0 | if ('function' == typeof options) { |
| 187 | 0 | fn = options, options = {}; |
| 188 | } | |
| 189 | ||
| 190 | // cache requires .filename | |
| 191 | 0 | if (options.cache && !options.filename) { |
| 192 | 0 | return fn(new Error('the "filename" option is required for caching')); |
| 193 | } | |
| 194 | ||
| 195 | 0 | try { |
| 196 | 0 | var path = options.filename; |
| 197 | 0 | var tmpl = options.cache |
| 198 | ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) | |
| 199 | : exports.compile(str, options); | |
| 200 | 0 | fn(null, tmpl(options)); |
| 201 | } catch (err) { | |
| 202 | 0 | fn(err); |
| 203 | } | |
| 204 | }; | |
| 205 | ||
| 206 | /** | |
| 207 | * Render a Jade file at the given `path` and callback `fn(err, str)`. | |
| 208 | * | |
| 209 | * @param {String} path | |
| 210 | * @param {Object|Function} options or callback | |
| 211 | * @param {Function} fn | |
| 212 | * @api public | |
| 213 | */ | |
| 214 | ||
| 215 | 1 | exports.renderFile = function(path, options, fn){ |
| 216 | 0 | var key = path + ':string'; |
| 217 | ||
| 218 | 0 | if ('function' == typeof options) { |
| 219 | 0 | fn = options, options = {}; |
| 220 | } | |
| 221 | ||
| 222 | 0 | try { |
| 223 | 0 | options.filename = path; |
| 224 | 0 | var str = options.cache |
| 225 | ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) | |
| 226 | : fs.readFileSync(path, 'utf8'); | |
| 227 | 0 | exports.render(str, options, fn); |
| 228 | } catch (err) { | |
| 229 | 0 | fn(err); |
| 230 | } | |
| 231 | }; | |
| 232 | ||
| 233 | /** | |
| 234 | * Express support. | |
| 235 | */ | |
| 236 | ||
| 237 | 1 | exports.__express = exports.renderFile; |
| 238 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - Lexer | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Initialize `Lexer` with the given `str`. | |
| 10 | * | |
| 11 | * Options: | |
| 12 | * | |
| 13 | * - `colons` allow colons for attr delimiters | |
| 14 | * | |
| 15 | * @param {String} str | |
| 16 | * @param {Object} options | |
| 17 | * @api private | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | var Lexer = module.exports = function Lexer(str, options) { |
| 21 | 2 | options = options || {}; |
| 22 | 2 | this.input = str.replace(/\r\n|\r/g, '\n'); |
| 23 | 2 | this.colons = options.colons; |
| 24 | 2 | this.deferredTokens = []; |
| 25 | 2 | this.lastIndents = 0; |
| 26 | 2 | this.lineno = 1; |
| 27 | 2 | this.stash = []; |
| 28 | 2 | this.indentStack = []; |
| 29 | 2 | this.indentRe = null; |
| 30 | 2 | this.pipeless = false; |
| 31 | }; | |
| 32 | ||
| 33 | /** | |
| 34 | * Lexer prototype. | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | Lexer.prototype = { |
| 38 | ||
| 39 | /** | |
| 40 | * Construct a token with the given `type` and `val`. | |
| 41 | * | |
| 42 | * @param {String} type | |
| 43 | * @param {String} val | |
| 44 | * @return {Object} | |
| 45 | * @api private | |
| 46 | */ | |
| 47 | ||
| 48 | tok: function(type, val){ | |
| 49 | 202 | return { |
| 50 | type: type | |
| 51 | , line: this.lineno | |
| 52 | , val: val | |
| 53 | } | |
| 54 | }, | |
| 55 | ||
| 56 | /** | |
| 57 | * Consume the given `len` of input. | |
| 58 | * | |
| 59 | * @param {Number} len | |
| 60 | * @api private | |
| 61 | */ | |
| 62 | ||
| 63 | consume: function(len){ | |
| 64 | 174 | this.input = this.input.substr(len); |
| 65 | }, | |
| 66 | ||
| 67 | /** | |
| 68 | * Scan for `type` with the given `regexp`. | |
| 69 | * | |
| 70 | * @param {String} type | |
| 71 | * @param {RegExp} regexp | |
| 72 | * @return {Object} | |
| 73 | * @api private | |
| 74 | */ | |
| 75 | ||
| 76 | scan: function(regexp, type){ | |
| 77 | 1549 | var captures; |
| 78 | 1549 | if (captures = regexp.exec(this.input)) { |
| 79 | 46 | this.consume(captures[0].length); |
| 80 | 46 | return this.tok(type, captures[1]); |
| 81 | } | |
| 82 | }, | |
| 83 | ||
| 84 | /** | |
| 85 | * Defer the given `tok`. | |
| 86 | * | |
| 87 | * @param {Object} tok | |
| 88 | * @api private | |
| 89 | */ | |
| 90 | ||
| 91 | defer: function(tok){ | |
| 92 | 28 | this.deferredTokens.push(tok); |
| 93 | }, | |
| 94 | ||
| 95 | /** | |
| 96 | * Lookahead `n` tokens. | |
| 97 | * | |
| 98 | * @param {Number} n | |
| 99 | * @return {Object} | |
| 100 | * @api private | |
| 101 | */ | |
| 102 | ||
| 103 | lookahead: function(n){ | |
| 104 | 798 | var fetch = n - this.stash.length; |
| 105 | 999 | while (fetch-- > 0) this.stash.push(this.next()); |
| 106 | 798 | return this.stash[--n]; |
| 107 | }, | |
| 108 | ||
| 109 | /** | |
| 110 | * Return the indexOf `start` / `end` delimiters. | |
| 111 | * | |
| 112 | * @param {String} start | |
| 113 | * @param {String} end | |
| 114 | * @return {Number} | |
| 115 | * @api private | |
| 116 | */ | |
| 117 | ||
| 118 | indexOfDelimiters: function(start, end){ | |
| 119 | 7 | var str = this.input |
| 120 | , nstart = 0 | |
| 121 | , nend = 0 | |
| 122 | , pos = 0; | |
| 123 | 7 | for (var i = 0, len = str.length; i < len; ++i) { |
| 124 | 213 | if (start == str.charAt(i)) { |
| 125 | 10 | ++nstart; |
| 126 | 203 | } else if (end == str.charAt(i)) { |
| 127 | 10 | if (++nend == nstart) { |
| 128 | 7 | pos = i; |
| 129 | 7 | break; |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | 7 | return pos; |
| 134 | }, | |
| 135 | ||
| 136 | /** | |
| 137 | * Stashed token. | |
| 138 | */ | |
| 139 | ||
| 140 | stashed: function() { | |
| 141 | 214 | return this.stash.length |
| 142 | && this.stash.shift(); | |
| 143 | }, | |
| 144 | ||
| 145 | /** | |
| 146 | * Deferred token. | |
| 147 | */ | |
| 148 | ||
| 149 | deferred: function() { | |
| 150 | 204 | return this.deferredTokens.length |
| 151 | && this.deferredTokens.shift(); | |
| 152 | }, | |
| 153 | ||
| 154 | /** | |
| 155 | * end-of-source. | |
| 156 | */ | |
| 157 | ||
| 158 | eos: function() { | |
| 159 | 344 | if (this.input.length) return; |
| 160 | 2 | if (this.indentStack.length) { |
| 161 | 0 | this.indentStack.shift(); |
| 162 | 0 | return this.tok('outdent'); |
| 163 | } else { | |
| 164 | 2 | return this.tok('eos'); |
| 165 | } | |
| 166 | }, | |
| 167 | ||
| 168 | /** | |
| 169 | * Blank line. | |
| 170 | */ | |
| 171 | ||
| 172 | blank: function() { | |
| 173 | 176 | var captures; |
| 174 | 176 | if (captures = /^\n *\n/.exec(this.input)) { |
| 175 | 3 | this.consume(captures[0].length - 1); |
| 176 | 3 | if (this.pipeless) return this.tok('text', ''); |
| 177 | 3 | return this.next(); |
| 178 | } | |
| 179 | }, | |
| 180 | ||
| 181 | /** | |
| 182 | * Comment. | |
| 183 | */ | |
| 184 | ||
| 185 | comment: function() { | |
| 186 | 11 | var captures; |
| 187 | 11 | if (captures = /^ *\/\/(-)?([^\n]*)/.exec(this.input)) { |
| 188 | 0 | this.consume(captures[0].length); |
| 189 | 0 | var tok = this.tok('comment', captures[2]); |
| 190 | 0 | tok.buffer = '-' != captures[1]; |
| 191 | 0 | return tok; |
| 192 | } | |
| 193 | }, | |
| 194 | ||
| 195 | /** | |
| 196 | * Interpolated tag. | |
| 197 | */ | |
| 198 | ||
| 199 | interpolation: function() { | |
| 200 | 170 | var captures; |
| 201 | 170 | if (captures = /^#\{(.*?)\}/.exec(this.input)) { |
| 202 | 0 | this.consume(captures[0].length); |
| 203 | 0 | return this.tok('interpolation', captures[1]); |
| 204 | } | |
| 205 | }, | |
| 206 | ||
| 207 | /** | |
| 208 | * Tag. | |
| 209 | */ | |
| 210 | ||
| 211 | tag: function() { | |
| 212 | 158 | var captures; |
| 213 | 158 | if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) { |
| 214 | 33 | this.consume(captures[0].length); |
| 215 | 33 | var tok, name = captures[1]; |
| 216 | 33 | if (':' == name[name.length - 1]) { |
| 217 | 0 | name = name.slice(0, -1); |
| 218 | 0 | tok = this.tok('tag', name); |
| 219 | 0 | this.defer(this.tok(':')); |
| 220 | 0 | while (' ' == this.input[0]) this.input = this.input.substr(1); |
| 221 | } else { | |
| 222 | 33 | tok = this.tok('tag', name); |
| 223 | } | |
| 224 | 33 | tok.selfClosing = !! captures[2]; |
| 225 | 33 | return tok; |
| 226 | } | |
| 227 | }, | |
| 228 | ||
| 229 | /** | |
| 230 | * Filter. | |
| 231 | */ | |
| 232 | ||
| 233 | filter: function() { | |
| 234 | 125 | return this.scan(/^:(\w+)/, 'filter'); |
| 235 | }, | |
| 236 | ||
| 237 | /** | |
| 238 | * Doctype. | |
| 239 | */ | |
| 240 | ||
| 241 | doctype: function() { | |
| 242 | 171 | return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype'); |
| 243 | }, | |
| 244 | ||
| 245 | /** | |
| 246 | * Id. | |
| 247 | */ | |
| 248 | ||
| 249 | id: function() { | |
| 250 | 109 | return this.scan(/^#([\w-]+)/, 'id'); |
| 251 | }, | |
| 252 | ||
| 253 | /** | |
| 254 | * Class. | |
| 255 | */ | |
| 256 | ||
| 257 | className: function() { | |
| 258 | 101 | return this.scan(/^\.([\w-]+)/, 'class'); |
| 259 | }, | |
| 260 | ||
| 261 | /** | |
| 262 | * Text. | |
| 263 | */ | |
| 264 | ||
| 265 | text: function() { | |
| 266 | 11 | return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text'); |
| 267 | }, | |
| 268 | ||
| 269 | /** | |
| 270 | * Extends. | |
| 271 | */ | |
| 272 | ||
| 273 | "extends": function() { | |
| 274 | 170 | return this.scan(/^extends? +([^\n]+)/, 'extends'); |
| 275 | }, | |
| 276 | ||
| 277 | /** | |
| 278 | * Block prepend. | |
| 279 | */ | |
| 280 | ||
| 281 | prepend: function() { | |
| 282 | 170 | var captures; |
| 283 | 170 | if (captures = /^prepend +([^\n]+)/.exec(this.input)) { |
| 284 | 0 | this.consume(captures[0].length); |
| 285 | 0 | var mode = 'prepend' |
| 286 | , name = captures[1] | |
| 287 | , tok = this.tok('block', name); | |
| 288 | 0 | tok.mode = mode; |
| 289 | 0 | return tok; |
| 290 | } | |
| 291 | }, | |
| 292 | ||
| 293 | /** | |
| 294 | * Block append. | |
| 295 | */ | |
| 296 | ||
| 297 | append: function() { | |
| 298 | 170 | var captures; |
| 299 | 170 | if (captures = /^append +([^\n]+)/.exec(this.input)) { |
| 300 | 0 | this.consume(captures[0].length); |
| 301 | 0 | var mode = 'append' |
| 302 | , name = captures[1] | |
| 303 | , tok = this.tok('block', name); | |
| 304 | 0 | tok.mode = mode; |
| 305 | 0 | return tok; |
| 306 | } | |
| 307 | }, | |
| 308 | ||
| 309 | /** | |
| 310 | * Block. | |
| 311 | */ | |
| 312 | ||
| 313 | block: function() { | |
| 314 | 170 | var captures; |
| 315 | 170 | if (captures = /^block\b *(?:(prepend|append) +)?([^\n]*)/.exec(this.input)) { |
| 316 | 0 | this.consume(captures[0].length); |
| 317 | 0 | var mode = captures[1] || 'replace' |
| 318 | , name = captures[2] | |
| 319 | , tok = this.tok('block', name); | |
| 320 | ||
| 321 | 0 | tok.mode = mode; |
| 322 | 0 | return tok; |
| 323 | } | |
| 324 | }, | |
| 325 | ||
| 326 | /** | |
| 327 | * Yield. | |
| 328 | */ | |
| 329 | ||
| 330 | yield: function() { | |
| 331 | 171 | return this.scan(/^yield */, 'yield'); |
| 332 | }, | |
| 333 | ||
| 334 | /** | |
| 335 | * Include. | |
| 336 | */ | |
| 337 | ||
| 338 | include: function() { | |
| 339 | 170 | return this.scan(/^include +([^\n]+)/, 'include'); |
| 340 | }, | |
| 341 | ||
| 342 | /** | |
| 343 | * Case. | |
| 344 | */ | |
| 345 | ||
| 346 | "case": function() { | |
| 347 | 170 | return this.scan(/^case +([^\n]+)/, 'case'); |
| 348 | }, | |
| 349 | ||
| 350 | /** | |
| 351 | * When. | |
| 352 | */ | |
| 353 | ||
| 354 | when: function() { | |
| 355 | 170 | return this.scan(/^when +([^:\n]+)/, 'when'); |
| 356 | }, | |
| 357 | ||
| 358 | /** | |
| 359 | * Default. | |
| 360 | */ | |
| 361 | ||
| 362 | "default": function() { | |
| 363 | 170 | return this.scan(/^default */, 'default'); |
| 364 | }, | |
| 365 | ||
| 366 | /** | |
| 367 | * Assignment. | |
| 368 | */ | |
| 369 | ||
| 370 | assignment: function() { | |
| 371 | 160 | var captures; |
| 372 | 160 | if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) { |
| 373 | 2 | this.consume(captures[0].length); |
| 374 | 2 | var name = captures[1] |
| 375 | , val = captures[2]; | |
| 376 | 2 | return this.tok('code', 'var ' + name + ' = (' + val + ');'); |
| 377 | } | |
| 378 | }, | |
| 379 | ||
| 380 | /** | |
| 381 | * Call mixin. | |
| 382 | */ | |
| 383 | ||
| 384 | call: function(){ | |
| 385 | 167 | var captures; |
| 386 | 167 | if (captures = /^\+([-\w]+)/.exec(this.input)) { |
| 387 | 0 | this.consume(captures[0].length); |
| 388 | 0 | var tok = this.tok('call', captures[1]); |
| 389 | ||
| 390 | // Check for args (not attributes) | |
| 391 | 0 | if (captures = /^ *\((.*?)\)/.exec(this.input)) { |
| 392 | 0 | if (!/^ *[-\w]+ *=/.test(captures[1])) { |
| 393 | 0 | this.consume(captures[0].length); |
| 394 | 0 | tok.args = captures[1]; |
| 395 | } | |
| 396 | } | |
| 397 | ||
| 398 | 0 | return tok; |
| 399 | } | |
| 400 | }, | |
| 401 | ||
| 402 | /** | |
| 403 | * Mixin. | |
| 404 | */ | |
| 405 | ||
| 406 | mixin: function(){ | |
| 407 | 167 | var captures; |
| 408 | 167 | if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) { |
| 409 | 0 | this.consume(captures[0].length); |
| 410 | 0 | var tok = this.tok('mixin', captures[1]); |
| 411 | 0 | tok.args = captures[2]; |
| 412 | 0 | return tok; |
| 413 | } | |
| 414 | }, | |
| 415 | ||
| 416 | /** | |
| 417 | * Conditional. | |
| 418 | */ | |
| 419 | ||
| 420 | conditional: function() { | |
| 421 | 167 | var captures; |
| 422 | 167 | if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) { |
| 423 | 4 | this.consume(captures[0].length); |
| 424 | 4 | var type = captures[1] |
| 425 | , js = captures[2]; | |
| 426 | ||
| 427 | 4 | switch (type) { |
| 428 | 4 | case 'if': js = 'if (' + js + ')'; break; |
| 429 | 0 | case 'unless': js = 'if (!(' + js + '))'; break; |
| 430 | 2 | case 'else if': js = 'else if (' + js + ')'; break; |
| 431 | 2 | case 'else': js = 'else'; break; |
| 432 | } | |
| 433 | ||
| 434 | 4 | return this.tok('code', js); |
| 435 | } | |
| 436 | }, | |
| 437 | ||
| 438 | /** | |
| 439 | * While. | |
| 440 | */ | |
| 441 | ||
| 442 | "while": function() { | |
| 443 | 160 | var captures; |
| 444 | 160 | if (captures = /^while +([^\n]+)/.exec(this.input)) { |
| 445 | 0 | this.consume(captures[0].length); |
| 446 | 0 | return this.tok('code', 'while (' + captures[1] + ')'); |
| 447 | } | |
| 448 | }, | |
| 449 | ||
| 450 | /** | |
| 451 | * Each. | |
| 452 | */ | |
| 453 | ||
| 454 | each: function() { | |
| 455 | 163 | var captures; |
| 456 | 163 | if (captures = /^(?:- *)?(?:each|for) +(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) { |
| 457 | 3 | this.consume(captures[0].length); |
| 458 | 3 | var tok = this.tok('each', captures[1]); |
| 459 | 3 | tok.key = captures[2] || '$index'; |
| 460 | 3 | tok.code = captures[3]; |
| 461 | 3 | return tok; |
| 462 | } | |
| 463 | }, | |
| 464 | ||
| 465 | /** | |
| 466 | * Code. | |
| 467 | */ | |
| 468 | ||
| 469 | code: function() { | |
| 470 | 125 | var captures; |
| 471 | 125 | if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) { |
| 472 | 16 | this.consume(captures[0].length); |
| 473 | 16 | var flags = captures[1]; |
| 474 | 16 | captures[1] = captures[2]; |
| 475 | 16 | var tok = this.tok('code', captures[1]); |
| 476 | 16 | tok.escape = flags[0] === '='; |
| 477 | 16 | tok.buffer = flags[0] === '=' || flags[1] === '='; |
| 478 | 16 | return tok; |
| 479 | } | |
| 480 | }, | |
| 481 | ||
| 482 | /** | |
| 483 | * Attributes. | |
| 484 | */ | |
| 485 | ||
| 486 | attrs: function() { | |
| 487 | 78 | if ('(' == this.input.charAt(0)) { |
| 488 | 7 | var index = this.indexOfDelimiters('(', ')') |
| 489 | , str = this.input.substr(1, index-1) | |
| 490 | , tok = this.tok('attrs') | |
| 491 | , len = str.length | |
| 492 | , colons = this.colons | |
| 493 | , states = ['key'] | |
| 494 | , escapedAttr | |
| 495 | , key = '' | |
| 496 | , val = '' | |
| 497 | , quote | |
| 498 | , c | |
| 499 | , p; | |
| 500 | ||
| 501 | 7 | function state(){ |
| 502 | 206 | return states[states.length - 1]; |
| 503 | } | |
| 504 | ||
| 505 | 7 | function interpolate(attr) { |
| 506 | 7 | return attr.replace(/#\{([^}]+)\}/g, function(_, expr){ |
| 507 | 1 | return quote + " + (" + expr + ") + " + quote; |
| 508 | }); | |
| 509 | } | |
| 510 | ||
| 511 | 7 | this.consume(index + 1); |
| 512 | 7 | tok.attrs = {}; |
| 513 | 7 | tok.escaped = {}; |
| 514 | ||
| 515 | 7 | function parse(c) { |
| 516 | 206 | var real = c; |
| 517 | // TODO: remove when people fix ":" | |
| 518 | 206 | if (colons && ':' == c) c = '='; |
| 519 | 206 | switch (c) { |
| 520 | case ',': | |
| 521 | case '\n': | |
| 522 | 7 | switch (state()) { |
| 523 | case 'expr': | |
| 524 | case 'array': | |
| 525 | case 'string': | |
| 526 | case 'object': | |
| 527 | 0 | val += c; |
| 528 | 0 | break; |
| 529 | default: | |
| 530 | 7 | states.push('key'); |
| 531 | 7 | val = val.trim(); |
| 532 | 7 | key = key.trim(); |
| 533 | 7 | if ('' == key) return; |
| 534 | 7 | key = key.replace(/^['"]|['"]$/g, '').replace('!', ''); |
| 535 | 7 | tok.escaped[key] = escapedAttr; |
| 536 | 7 | tok.attrs[key] = '' == val |
| 537 | ? true | |
| 538 | : interpolate(val); | |
| 539 | 7 | key = val = ''; |
| 540 | } | |
| 541 | 7 | break; |
| 542 | case '=': | |
| 543 | 7 | switch (state()) { |
| 544 | case 'key char': | |
| 545 | 0 | key += real; |
| 546 | 0 | break; |
| 547 | case 'val': | |
| 548 | case 'expr': | |
| 549 | case 'array': | |
| 550 | case 'string': | |
| 551 | case 'object': | |
| 552 | 0 | val += real; |
| 553 | 0 | break; |
| 554 | default: | |
| 555 | 7 | escapedAttr = '!' != p; |
| 556 | 7 | states.push('val'); |
| 557 | } | |
| 558 | 7 | break; |
| 559 | case '(': | |
| 560 | 3 | if ('val' == state() |
| 561 | 3 | || 'expr' == state()) states.push('expr'); |
| 562 | 3 | val += c; |
| 563 | 3 | break; |
| 564 | case ')': | |
| 565 | 3 | if ('expr' == state() |
| 566 | 3 | || 'val' == state()) states.pop(); |
| 567 | 3 | val += c; |
| 568 | 3 | break; |
| 569 | case '{': | |
| 570 | 1 | if ('val' == state()) states.push('object'); |
| 571 | 1 | val += c; |
| 572 | 1 | break; |
| 573 | case '}': | |
| 574 | 1 | if ('object' == state()) states.pop(); |
| 575 | 1 | val += c; |
| 576 | 1 | break; |
| 577 | case '[': | |
| 578 | 0 | if ('val' == state()) states.push('array'); |
| 579 | 0 | val += c; |
| 580 | 0 | break; |
| 581 | case ']': | |
| 582 | 0 | if ('array' == state()) states.pop(); |
| 583 | 0 | val += c; |
| 584 | 0 | break; |
| 585 | case '"': | |
| 586 | case "'": | |
| 587 | 6 | switch (state()) { |
| 588 | case 'key': | |
| 589 | 0 | states.push('key char'); |
| 590 | 0 | break; |
| 591 | case 'key char': | |
| 592 | 0 | states.pop(); |
| 593 | 0 | break; |
| 594 | case 'string': | |
| 595 | 6 | if (c == quote) states.pop(); |
| 596 | 3 | val += c; |
| 597 | 3 | break; |
| 598 | default: | |
| 599 | 3 | states.push('string'); |
| 600 | 3 | val += c; |
| 601 | 3 | quote = c; |
| 602 | } | |
| 603 | 6 | break; |
| 604 | case '': | |
| 605 | 0 | break; |
| 606 | default: | |
| 607 | 178 | switch (state()) { |
| 608 | case 'key': | |
| 609 | case 'key char': | |
| 610 | 29 | key += c; |
| 611 | 29 | break; |
| 612 | default: | |
| 613 | 149 | val += c; |
| 614 | } | |
| 615 | } | |
| 616 | 206 | p = c; |
| 617 | } | |
| 618 | ||
| 619 | 7 | for (var i = 0; i < len; ++i) { |
| 620 | 199 | parse(str.charAt(i)); |
| 621 | } | |
| 622 | ||
| 623 | 7 | parse(','); |
| 624 | ||
| 625 | 7 | if ('/' == this.input.charAt(0)) { |
| 626 | 0 | this.consume(1); |
| 627 | 0 | tok.selfClosing = true; |
| 628 | } | |
| 629 | ||
| 630 | 7 | return tok; |
| 631 | } | |
| 632 | }, | |
| 633 | ||
| 634 | /** | |
| 635 | * Indent | Outdent | Newline. | |
| 636 | */ | |
| 637 | ||
| 638 | indent: function() { | |
| 639 | 71 | var captures, re; |
| 640 | ||
| 641 | // established regexp | |
| 642 | 71 | if (this.indentRe) { |
| 643 | 68 | captures = this.indentRe.exec(this.input); |
| 644 | // determine regexp | |
| 645 | } else { | |
| 646 | // tabs | |
| 647 | 3 | re = /^\n(\t*) */; |
| 648 | 3 | captures = re.exec(this.input); |
| 649 | ||
| 650 | // spaces | |
| 651 | 3 | if (captures && !captures[1].length) { |
| 652 | 3 | re = /^\n( *)/; |
| 653 | 3 | captures = re.exec(this.input); |
| 654 | } | |
| 655 | ||
| 656 | // established | |
| 657 | 5 | if (captures && captures[1].length) this.indentRe = re; |
| 658 | } | |
| 659 | ||
| 660 | 71 | if (captures) { |
| 661 | 60 | var tok |
| 662 | , indents = captures[1].length; | |
| 663 | ||
| 664 | 60 | ++this.lineno; |
| 665 | 60 | this.consume(indents + 1); |
| 666 | ||
| 667 | 60 | if (' ' == this.input[0] || '\t' == this.input[0]) { |
| 668 | 0 | throw new Error('Invalid indentation, you can use tabs or spaces but not both'); |
| 669 | } | |
| 670 | ||
| 671 | // blank line | |
| 672 | 60 | if ('\n' == this.input[0]) return this.tok('newline'); |
| 673 | ||
| 674 | // outdent | |
| 675 | 60 | if (this.indentStack.length && indents < this.indentStack[0]) { |
| 676 | 11 | while (this.indentStack.length && this.indentStack[0] > indents) { |
| 677 | 26 | this.stash.push(this.tok('outdent')); |
| 678 | 26 | this.indentStack.shift(); |
| 679 | } | |
| 680 | 11 | tok = this.stash.pop(); |
| 681 | // indent | |
| 682 | 49 | } else if (indents && indents != this.indentStack[0]) { |
| 683 | 26 | this.indentStack.unshift(indents); |
| 684 | 26 | tok = this.tok('indent', indents); |
| 685 | // newline | |
| 686 | } else { | |
| 687 | 23 | tok = this.tok('newline'); |
| 688 | } | |
| 689 | ||
| 690 | 60 | return tok; |
| 691 | } | |
| 692 | }, | |
| 693 | ||
| 694 | /** | |
| 695 | * Pipe-less text consumed only when | |
| 696 | * pipeless is true; | |
| 697 | */ | |
| 698 | ||
| 699 | pipelessText: function() { | |
| 700 | 171 | if (this.pipeless) { |
| 701 | 0 | if ('\n' == this.input[0]) return; |
| 702 | 0 | var i = this.input.indexOf('\n'); |
| 703 | 0 | if (-1 == i) i = this.input.length; |
| 704 | 0 | var str = this.input.substr(0, i); |
| 705 | 0 | this.consume(str.length); |
| 706 | 0 | return this.tok('text', str); |
| 707 | } | |
| 708 | }, | |
| 709 | ||
| 710 | /** | |
| 711 | * ':' | |
| 712 | */ | |
| 713 | ||
| 714 | colon: function() { | |
| 715 | 11 | return this.scan(/^: */, ':'); |
| 716 | }, | |
| 717 | ||
| 718 | /** | |
| 719 | * Return the next token object, or those | |
| 720 | * previously stashed by lookahead. | |
| 721 | * | |
| 722 | * @return {Object} | |
| 723 | * @api private | |
| 724 | */ | |
| 725 | ||
| 726 | advance: function(){ | |
| 727 | 214 | return this.stashed() |
| 728 | || this.next(); | |
| 729 | }, | |
| 730 | ||
| 731 | /** | |
| 732 | * Return the next token object. | |
| 733 | * | |
| 734 | * @return {Object} | |
| 735 | * @api private | |
| 736 | */ | |
| 737 | ||
| 738 | next: function() { | |
| 739 | 204 | return this.deferred() |
| 740 | || this.blank() | |
| 741 | || this.eos() | |
| 742 | || this.pipelessText() | |
| 743 | || this.yield() | |
| 744 | || this.doctype() | |
| 745 | || this.interpolation() | |
| 746 | || this["case"]() | |
| 747 | || this.when() | |
| 748 | || this["default"]() | |
| 749 | || this["extends"]() | |
| 750 | || this.append() | |
| 751 | || this.prepend() | |
| 752 | || this.block() | |
| 753 | || this.include() | |
| 754 | || this.mixin() | |
| 755 | || this.call() | |
| 756 | || this.conditional() | |
| 757 | || this.each() | |
| 758 | || this["while"]() | |
| 759 | || this.assignment() | |
| 760 | || this.tag() | |
| 761 | || this.filter() | |
| 762 | || this.code() | |
| 763 | || this.id() | |
| 764 | || this.className() | |
| 765 | || this.attrs() | |
| 766 | || this.indent() | |
| 767 | || this.comment() | |
| 768 | || this.colon() | |
| 769 | || this.text(); | |
| 770 | } | |
| 771 | }; | |
| 772 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Attrs | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'), |
| 13 | Block = require('./block'); | |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a `Attrs` node. | |
| 17 | * | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Attrs = module.exports = function Attrs() { |
| 22 | 0 | this.attrs = []; |
| 23 | }; | |
| 24 | ||
| 25 | /** | |
| 26 | * Inherit from `Node`. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | Attrs.prototype.__proto__ = Node.prototype; |
| 30 | ||
| 31 | /** | |
| 32 | * Set attribute `name` to `val`, keep in mind these become | |
| 33 | * part of a raw js object literal, so to quote a value you must | |
| 34 | * '"quote me"', otherwise or example 'user.name' is literal JavaScript. | |
| 35 | * | |
| 36 | * @param {String} name | |
| 37 | * @param {String} val | |
| 38 | * @param {Boolean} escaped | |
| 39 | * @return {Tag} for chaining | |
| 40 | * @api public | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | Attrs.prototype.setAttribute = function(name, val, escaped){ |
| 44 | 38 | this.attrs.push({ name: name, val: val, escaped: escaped }); |
| 45 | 38 | return this; |
| 46 | }; | |
| 47 | ||
| 48 | /** | |
| 49 | * Remove attribute `name` when present. | |
| 50 | * | |
| 51 | * @param {String} name | |
| 52 | * @api public | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | Attrs.prototype.removeAttribute = function(name){ |
| 56 | 0 | for (var i = 0, len = this.attrs.length; i < len; ++i) { |
| 57 | 0 | if (this.attrs[i] && this.attrs[i].name == name) { |
| 58 | 0 | delete this.attrs[i]; |
| 59 | } | |
| 60 | } | |
| 61 | }; | |
| 62 | ||
| 63 | /** | |
| 64 | * Get attribute value by `name`. | |
| 65 | * | |
| 66 | * @param {String} name | |
| 67 | * @return {String} | |
| 68 | * @api public | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | Attrs.prototype.getAttribute = function(name){ |
| 72 | 0 | for (var i = 0, len = this.attrs.length; i < len; ++i) { |
| 73 | 0 | if (this.attrs[i] && this.attrs[i].name == name) { |
| 74 | 0 | return this.attrs[i].val; |
| 75 | } | |
| 76 | } | |
| 77 | }; | |
| 78 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - BlockComment | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `BlockComment` with the given `block`. | |
| 16 | * | |
| 17 | * @param {String} val | |
| 18 | * @param {Block} block | |
| 19 | * @param {Boolean} buffer | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | var BlockComment = module.exports = function BlockComment(val, block, buffer) { |
| 24 | 0 | this.block = block; |
| 25 | 0 | this.val = val; |
| 26 | 0 | this.buffer = buffer; |
| 27 | }; | |
| 28 | ||
| 29 | /** | |
| 30 | * Inherit from `Node`. | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | BlockComment.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Block | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Block` with an optional `node`. | |
| 16 | * | |
| 17 | * @param {Node} node | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Block = module.exports = function Block(node){ |
| 22 | 75 | this.nodes = []; |
| 23 | 75 | if (node) this.push(node); |
| 24 | }; | |
| 25 | ||
| 26 | /** | |
| 27 | * Inherit from `Node`. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | Block.prototype.__proto__ = Node.prototype; |
| 31 | ||
| 32 | /** | |
| 33 | * Block flag. | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | Block.prototype.isBlock = true; |
| 37 | ||
| 38 | /** | |
| 39 | * Replace the nodes in `other` with the nodes | |
| 40 | * in `this` block. | |
| 41 | * | |
| 42 | * @param {Block} other | |
| 43 | * @api private | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | Block.prototype.replace = function(other){ |
| 47 | 0 | other.nodes = this.nodes; |
| 48 | }; | |
| 49 | ||
| 50 | /** | |
| 51 | * Pust the given `node`. | |
| 52 | * | |
| 53 | * @param {Node} node | |
| 54 | * @return {Number} | |
| 55 | * @api public | |
| 56 | */ | |
| 57 | ||
| 58 | 1 | Block.prototype.push = function(node){ |
| 59 | 119 | return this.nodes.push(node); |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Check if this block is empty. | |
| 64 | * | |
| 65 | * @return {Boolean} | |
| 66 | * @api public | |
| 67 | */ | |
| 68 | ||
| 69 | 1 | Block.prototype.isEmpty = function(){ |
| 70 | 0 | return 0 == this.nodes.length; |
| 71 | }; | |
| 72 | ||
| 73 | /** | |
| 74 | * Unshift the given `node`. | |
| 75 | * | |
| 76 | * @param {Node} node | |
| 77 | * @return {Number} | |
| 78 | * @api public | |
| 79 | */ | |
| 80 | ||
| 81 | 1 | Block.prototype.unshift = function(node){ |
| 82 | 0 | return this.nodes.unshift(node); |
| 83 | }; | |
| 84 | ||
| 85 | /** | |
| 86 | * Return the "last" block, or the first `yield` node. | |
| 87 | * | |
| 88 | * @return {Block} | |
| 89 | * @api private | |
| 90 | */ | |
| 91 | ||
| 92 | 1 | Block.prototype.includeBlock = function(){ |
| 93 | 0 | var ret = this |
| 94 | , node; | |
| 95 | ||
| 96 | 0 | for (var i = 0, len = this.nodes.length; i < len; ++i) { |
| 97 | 0 | node = this.nodes[i]; |
| 98 | 0 | if (node.yield) return node; |
| 99 | 0 | else if (node.textOnly) continue; |
| 100 | 0 | else if (node.includeBlock) ret = node.includeBlock(); |
| 101 | 0 | else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock(); |
| 102 | } | |
| 103 | ||
| 104 | 0 | return ret; |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Return a clone of this block. | |
| 109 | * | |
| 110 | * @return {Block} | |
| 111 | * @api private | |
| 112 | */ | |
| 113 | ||
| 114 | 1 | Block.prototype.clone = function(){ |
| 115 | 0 | var clone = new Block; |
| 116 | 0 | for (var i = 0, len = this.nodes.length; i < len; ++i) { |
| 117 | 0 | clone.push(this.nodes[i].clone()); |
| 118 | } | |
| 119 | 0 | return clone; |
| 120 | }; | |
| 121 | ||
| 122 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Case | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Case` with `expr`. | |
| 16 | * | |
| 17 | * @param {String} expr | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Case = exports = module.exports = function Case(expr, block){ |
| 22 | 0 | this.expr = expr; |
| 23 | 0 | this.block = block; |
| 24 | }; | |
| 25 | ||
| 26 | /** | |
| 27 | * Inherit from `Node`. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | Case.prototype.__proto__ = Node.prototype; |
| 31 | ||
| 32 | 1 | var When = exports.When = function When(expr, block){ |
| 33 | 0 | this.expr = expr; |
| 34 | 0 | this.block = block; |
| 35 | 0 | this.debug = false; |
| 36 | }; | |
| 37 | ||
| 38 | /** | |
| 39 | * Inherit from `Node`. | |
| 40 | */ | |
| 41 | ||
| 42 | 1 | When.prototype.__proto__ = Node.prototype; |
| 43 | ||
| 44 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Code | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `Code` node with the given code `val`. | |
| 16 | * Code may also be optionally buffered and escaped. | |
| 17 | * | |
| 18 | * @param {String} val | |
| 19 | * @param {Boolean} buffer | |
| 20 | * @param {Boolean} escape | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var Code = module.exports = function Code(val, buffer, escape) { |
| 25 | 22 | this.val = val; |
| 26 | 22 | this.buffer = buffer; |
| 27 | 22 | this.escape = escape; |
| 28 | 24 | if (val.match(/^ *else/)) this.debug = false; |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Inherit from `Node`. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | Code.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Comment | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `Comment` with the given `val`, optionally `buffer`, | |
| 16 | * otherwise the comment may render in the output. | |
| 17 | * | |
| 18 | * @param {String} val | |
| 19 | * @param {Boolean} buffer | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | var Comment = module.exports = function Comment(val, buffer) { |
| 24 | 0 | this.val = val; |
| 25 | 0 | this.buffer = buffer; |
| 26 | }; | |
| 27 | ||
| 28 | /** | |
| 29 | * Inherit from `Node`. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | Comment.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Doctype | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `Doctype` with the given `val`. | |
| 16 | * | |
| 17 | * @param {String} val | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Doctype = module.exports = function Doctype(val) { |
| 22 | 1 | this.val = val; |
| 23 | }; | |
| 24 | ||
| 25 | /** | |
| 26 | * Inherit from `Node`. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | Doctype.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Each | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize an `Each` node, representing iteration | |
| 16 | * | |
| 17 | * @param {String} obj | |
| 18 | * @param {String} val | |
| 19 | * @param {String} key | |
| 20 | * @param {Block} block | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var Each = module.exports = function Each(obj, val, key, block) { |
| 25 | 3 | this.obj = obj; |
| 26 | 3 | this.val = val; |
| 27 | 3 | this.key = key; |
| 28 | 3 | this.block = block; |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Inherit from `Node`. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | Each.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Filter | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node') |
| 13 | , Block = require('./block'); | |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a `Filter` node with the given | |
| 17 | * filter `name` and `block`. | |
| 18 | * | |
| 19 | * @param {String} name | |
| 20 | * @param {Block|Node} block | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var Filter = module.exports = function Filter(name, block, attrs) { |
| 25 | 0 | this.name = name; |
| 26 | 0 | this.block = block; |
| 27 | 0 | this.attrs = attrs; |
| 28 | 0 | this.isASTFilter = !block.nodes.every(function(node){ return node.isText }); |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Inherit from `Node`. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | Filter.prototype.__proto__ = Node.prototype; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | exports.Node = require('./node'); |
| 9 | 1 | exports.Tag = require('./tag'); |
| 10 | 1 | exports.Code = require('./code'); |
| 11 | 1 | exports.Each = require('./each'); |
| 12 | 1 | exports.Case = require('./case'); |
| 13 | 1 | exports.Text = require('./text'); |
| 14 | 1 | exports.Block = require('./block'); |
| 15 | 1 | exports.Mixin = require('./mixin'); |
| 16 | 1 | exports.Filter = require('./filter'); |
| 17 | 1 | exports.Comment = require('./comment'); |
| 18 | 1 | exports.Literal = require('./literal'); |
| 19 | 1 | exports.BlockComment = require('./block-comment'); |
| 20 | 1 | exports.Doctype = require('./doctype'); |
| 21 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Literal | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `Literal` node with the given `str. | |
| 16 | * | |
| 17 | * @param {String} str | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Literal = module.exports = function Literal(str) { |
| 22 | 2 | this.str = str |
| 23 | .replace(/\\/g, "\\\\") | |
| 24 | .replace(/\n|\r\n/g, "\\n") | |
| 25 | .replace(/'/g, "\\'"); | |
| 26 | }; | |
| 27 | ||
| 28 | /** | |
| 29 | * Inherit from `Node`. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | Literal.prototype.__proto__ = Node.prototype; |
| 33 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Mixin | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Attrs = require('./attrs'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a new `Mixin` with `name` and `block`. | |
| 16 | * | |
| 17 | * @param {String} name | |
| 18 | * @param {String} args | |
| 19 | * @param {Block} block | |
| 20 | * @api public | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | var Mixin = module.exports = function Mixin(name, args, block, call){ |
| 24 | 0 | this.name = name; |
| 25 | 0 | this.args = args; |
| 26 | 0 | this.block = block; |
| 27 | 0 | this.attrs = []; |
| 28 | 0 | this.call = call; |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Inherit from `Attrs`. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | Mixin.prototype.__proto__ = Attrs.prototype; |
| 36 | ||
| 37 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Node | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Initialize a `Node`. | |
| 10 | * | |
| 11 | * @api public | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | var Node = module.exports = function Node(){}; |
| 15 | ||
| 16 | /** | |
| 17 | * Clone this node (return itself) | |
| 18 | * | |
| 19 | * @return {Node} | |
| 20 | * @api private | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | Node.prototype.clone = function(){ |
| 24 | 0 | return this; |
| 25 | }; | |
| 26 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Tag | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Attrs = require('./attrs'), |
| 13 | Block = require('./block'), | |
| 14 | inlineTags = require('../inline-tags'); | |
| 15 | ||
| 16 | /** | |
| 17 | * Initialize a `Tag` node with the given tag `name` and optional `block`. | |
| 18 | * | |
| 19 | * @param {String} name | |
| 20 | * @param {Block} block | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var Tag = module.exports = function Tag(name, block) { |
| 25 | 47 | this.name = name; |
| 26 | 47 | this.attrs = []; |
| 27 | 47 | this.block = block || new Block; |
| 28 | }; | |
| 29 | ||
| 30 | /** | |
| 31 | * Inherit from `Attrs`. | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | Tag.prototype.__proto__ = Attrs.prototype; |
| 35 | ||
| 36 | /** | |
| 37 | * Clone this tag. | |
| 38 | * | |
| 39 | * @return {Tag} | |
| 40 | * @api private | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | Tag.prototype.clone = function(){ |
| 44 | 0 | var clone = new Tag(this.name, this.block.clone()); |
| 45 | 0 | clone.line = this.line; |
| 46 | 0 | clone.attrs = this.attrs; |
| 47 | 0 | clone.textOnly = this.textOnly; |
| 48 | 0 | return clone; |
| 49 | }; | |
| 50 | ||
| 51 | /** | |
| 52 | * Check if this tag is an inline tag. | |
| 53 | * | |
| 54 | * @return {Boolean} | |
| 55 | * @api private | |
| 56 | */ | |
| 57 | ||
| 58 | 1 | Tag.prototype.isInline = function(){ |
| 59 | 0 | return ~inlineTags.indexOf(this.name); |
| 60 | }; | |
| 61 | ||
| 62 | /** | |
| 63 | * Check if this tag's contents can be inlined. Used for pretty printing. | |
| 64 | * | |
| 65 | * @return {Boolean} | |
| 66 | * @api private | |
| 67 | */ | |
| 68 | ||
| 69 | 1 | Tag.prototype.canInline = function(){ |
| 70 | 0 | var nodes = this.block.nodes; |
| 71 | ||
| 72 | 0 | function isInline(node){ |
| 73 | // Recurse if the node is a block | |
| 74 | 0 | if (node.isBlock) return node.nodes.every(isInline); |
| 75 | 0 | return node.isText || (node.isInline && node.isInline()); |
| 76 | } | |
| 77 | ||
| 78 | // Empty tag | |
| 79 | 0 | if (!nodes.length) return true; |
| 80 | ||
| 81 | // Text-only or inline-only tag | |
| 82 | 0 | if (1 == nodes.length) return isInline(nodes[0]); |
| 83 | ||
| 84 | // Multi-line inline-only tag | |
| 85 | 0 | if (this.block.nodes.every(isInline)) { |
| 86 | 0 | for (var i = 1, len = nodes.length; i < len; ++i) { |
| 87 | 0 | if (nodes[i-1].isText && nodes[i].isText) |
| 88 | 0 | return false; |
| 89 | } | |
| 90 | 0 | return true; |
| 91 | } | |
| 92 | ||
| 93 | // Mixed tag | |
| 94 | 0 | return false; |
| 95 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - nodes - Text | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Node = require('./node'); |
| 13 | ||
| 14 | /** | |
| 15 | * Initialize a `Text` node with optional `line`. | |
| 16 | * | |
| 17 | * @param {String} line | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | var Text = module.exports = function Text(line) { |
| 22 | 11 | this.val = ''; |
| 23 | 22 | if ('string' == typeof line) this.val = line; |
| 24 | }; | |
| 25 | ||
| 26 | /** | |
| 27 | * Inherit from `Node`. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | Text.prototype.__proto__ = Node.prototype; |
| 31 | ||
| 32 | /** | |
| 33 | * Flag as text. | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | Text.prototype.isText = true; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - Parser | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Lexer = require('./lexer') |
| 13 | , nodes = require('./nodes'); | |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize `Parser` with the given input `str` and `filename`. | |
| 17 | * | |
| 18 | * @param {String} str | |
| 19 | * @param {String} filename | |
| 20 | * @param {Object} options | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var Parser = exports = module.exports = function Parser(str, filename, options){ |
| 25 | 2 | this.input = str; |
| 26 | 2 | this.lexer = new Lexer(str, options); |
| 27 | 2 | this.filename = filename; |
| 28 | 2 | this.blocks = {}; |
| 29 | 2 | this.mixins = {}; |
| 30 | 2 | this.options = options; |
| 31 | 2 | this.contexts = [this]; |
| 32 | }; | |
| 33 | ||
| 34 | /** | |
| 35 | * Tags that may not contain tags. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | var textOnly = exports.textOnly = ['script', 'style']; |
| 39 | ||
| 40 | /** | |
| 41 | * Parser prototype. | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | Parser.prototype = { |
| 45 | ||
| 46 | /** | |
| 47 | * Push `parser` onto the context stack, | |
| 48 | * or pop and return a `Parser`. | |
| 49 | */ | |
| 50 | ||
| 51 | context: function(parser){ | |
| 52 | 2 | if (parser) { |
| 53 | 1 | this.contexts.push(parser); |
| 54 | } else { | |
| 55 | 1 | return this.contexts.pop(); |
| 56 | } | |
| 57 | }, | |
| 58 | ||
| 59 | /** | |
| 60 | * Return the next token object. | |
| 61 | * | |
| 62 | * @return {Object} | |
| 63 | * @api private | |
| 64 | */ | |
| 65 | ||
| 66 | advance: function(){ | |
| 67 | 214 | return this.lexer.advance(); |
| 68 | }, | |
| 69 | ||
| 70 | /** | |
| 71 | * Skip `n` tokens. | |
| 72 | * | |
| 73 | * @param {Number} n | |
| 74 | * @api private | |
| 75 | */ | |
| 76 | ||
| 77 | skip: function(n){ | |
| 78 | 4 | while (n--) this.advance(); |
| 79 | }, | |
| 80 | ||
| 81 | /** | |
| 82 | * Single token lookahead. | |
| 83 | * | |
| 84 | * @return {Object} | |
| 85 | * @api private | |
| 86 | */ | |
| 87 | ||
| 88 | peek: function() { | |
| 89 | 616 | return this.lookahead(1); |
| 90 | }, | |
| 91 | ||
| 92 | /** | |
| 93 | * Return lexer lineno. | |
| 94 | * | |
| 95 | * @return {Number} | |
| 96 | * @api private | |
| 97 | */ | |
| 98 | ||
| 99 | line: function() { | |
| 100 | 112 | return this.lexer.lineno; |
| 101 | }, | |
| 102 | ||
| 103 | /** | |
| 104 | * `n` token lookahead. | |
| 105 | * | |
| 106 | * @param {Number} n | |
| 107 | * @return {Object} | |
| 108 | * @api private | |
| 109 | */ | |
| 110 | ||
| 111 | lookahead: function(n){ | |
| 112 | 798 | return this.lexer.lookahead(n); |
| 113 | }, | |
| 114 | ||
| 115 | /** | |
| 116 | * Parse input returning a string of js for evaluation. | |
| 117 | * | |
| 118 | * @return {String} | |
| 119 | * @api public | |
| 120 | */ | |
| 121 | ||
| 122 | parse: function(){ | |
| 123 | 2 | var block = new nodes.Block, parser; |
| 124 | 2 | block.line = this.line(); |
| 125 | ||
| 126 | 2 | while ('eos' != this.peek().type) { |
| 127 | 4 | if ('newline' == this.peek().type) { |
| 128 | 1 | this.advance(); |
| 129 | } else { | |
| 130 | 3 | block.push(this.parseExpr()); |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | 2 | if (parser = this.extending) { |
| 135 | 0 | this.context(parser); |
| 136 | 0 | var ast = parser.parse(); |
| 137 | 0 | this.context(); |
| 138 | // hoist mixins | |
| 139 | 0 | for (var name in this.mixins) |
| 140 | 0 | ast.unshift(this.mixins[name]); |
| 141 | 0 | return ast; |
| 142 | } | |
| 143 | ||
| 144 | 2 | return block; |
| 145 | }, | |
| 146 | ||
| 147 | /** | |
| 148 | * Expect the given type, or throw an exception. | |
| 149 | * | |
| 150 | * @param {String} type | |
| 151 | * @api private | |
| 152 | */ | |
| 153 | ||
| 154 | expect: function(type){ | |
| 155 | 92 | if (this.peek().type === type) { |
| 156 | 92 | return this.advance(); |
| 157 | } else { | |
| 158 | 0 | throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); |
| 159 | } | |
| 160 | }, | |
| 161 | ||
| 162 | /** | |
| 163 | * Accept the given `type`. | |
| 164 | * | |
| 165 | * @param {String} type | |
| 166 | * @api private | |
| 167 | */ | |
| 168 | ||
| 169 | accept: function(type){ | |
| 170 | 0 | if (this.peek().type === type) { |
| 171 | 0 | return this.advance(); |
| 172 | } | |
| 173 | }, | |
| 174 | ||
| 175 | /** | |
| 176 | * tag | |
| 177 | * | doctype | |
| 178 | * | mixin | |
| 179 | * | include | |
| 180 | * | filter | |
| 181 | * | comment | |
| 182 | * | text | |
| 183 | * | each | |
| 184 | * | code | |
| 185 | * | yield | |
| 186 | * | id | |
| 187 | * | class | |
| 188 | * | interpolation | |
| 189 | */ | |
| 190 | ||
| 191 | parseExpr: function(){ | |
| 192 | 74 | switch (this.peek().type) { |
| 193 | case 'tag': | |
| 194 | 47 | return this.parseTag(); |
| 195 | case 'mixin': | |
| 196 | 0 | return this.parseMixin(); |
| 197 | case 'block': | |
| 198 | 0 | return this.parseBlock(); |
| 199 | case 'case': | |
| 200 | 0 | return this.parseCase(); |
| 201 | case 'when': | |
| 202 | 0 | return this.parseWhen(); |
| 203 | case 'default': | |
| 204 | 0 | return this.parseDefault(); |
| 205 | case 'extends': | |
| 206 | 0 | return this.parseExtends(); |
| 207 | case 'include': | |
| 208 | 3 | return this.parseInclude(); |
| 209 | case 'doctype': | |
| 210 | 1 | return this.parseDoctype(); |
| 211 | case 'filter': | |
| 212 | 0 | return this.parseFilter(); |
| 213 | case 'comment': | |
| 214 | 0 | return this.parseComment(); |
| 215 | case 'text': | |
| 216 | 0 | return this.parseText(); |
| 217 | case 'each': | |
| 218 | 3 | return this.parseEach(); |
| 219 | case 'code': | |
| 220 | 6 | return this.parseCode(); |
| 221 | case 'call': | |
| 222 | 0 | return this.parseCall(); |
| 223 | case 'interpolation': | |
| 224 | 0 | return this.parseInterpolation(); |
| 225 | case 'yield': | |
| 226 | 0 | this.advance(); |
| 227 | 0 | var block = new nodes.Block; |
| 228 | 0 | block.yield = true; |
| 229 | 0 | return block; |
| 230 | case 'id': | |
| 231 | case 'class': | |
| 232 | 14 | var tok = this.advance(); |
| 233 | 14 | this.lexer.defer(this.lexer.tok('tag', 'div')); |
| 234 | 14 | this.lexer.defer(tok); |
| 235 | 14 | return this.parseExpr(); |
| 236 | default: | |
| 237 | 0 | throw new Error('unexpected token "' + this.peek().type + '"'); |
| 238 | } | |
| 239 | }, | |
| 240 | ||
| 241 | /** | |
| 242 | * Text | |
| 243 | */ | |
| 244 | ||
| 245 | parseText: function(){ | |
| 246 | 11 | var tok = this.expect('text') |
| 247 | , node = new nodes.Text(tok.val); | |
| 248 | 11 | node.line = this.line(); |
| 249 | 11 | return node; |
| 250 | }, | |
| 251 | ||
| 252 | /** | |
| 253 | * ':' expr | |
| 254 | * | block | |
| 255 | */ | |
| 256 | ||
| 257 | parseBlockExpansion: function(){ | |
| 258 | 0 | if (':' == this.peek().type) { |
| 259 | 0 | this.advance(); |
| 260 | 0 | return new nodes.Block(this.parseExpr()); |
| 261 | } else { | |
| 262 | 0 | return this.block(); |
| 263 | } | |
| 264 | }, | |
| 265 | ||
| 266 | /** | |
| 267 | * case | |
| 268 | */ | |
| 269 | ||
| 270 | parseCase: function(){ | |
| 271 | 0 | var val = this.expect('case').val |
| 272 | , node = new nodes.Case(val); | |
| 273 | 0 | node.line = this.line(); |
| 274 | 0 | node.block = this.block(); |
| 275 | 0 | return node; |
| 276 | }, | |
| 277 | ||
| 278 | /** | |
| 279 | * when | |
| 280 | */ | |
| 281 | ||
| 282 | parseWhen: function(){ | |
| 283 | 0 | var val = this.expect('when').val |
| 284 | 0 | return new nodes.Case.When(val, this.parseBlockExpansion()); |
| 285 | }, | |
| 286 | ||
| 287 | /** | |
| 288 | * default | |
| 289 | */ | |
| 290 | ||
| 291 | parseDefault: function(){ | |
| 292 | 0 | this.expect('default'); |
| 293 | 0 | return new nodes.Case.When('default', this.parseBlockExpansion()); |
| 294 | }, | |
| 295 | ||
| 296 | /** | |
| 297 | * code | |
| 298 | */ | |
| 299 | ||
| 300 | parseCode: function(){ | |
| 301 | 22 | var tok = this.expect('code') |
| 302 | , node = new nodes.Code(tok.val, tok.buffer, tok.escape) | |
| 303 | , block | |
| 304 | , i = 1; | |
| 305 | 22 | node.line = this.line(); |
| 306 | 33 | while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; |
| 307 | 22 | block = 'indent' == this.lookahead(i).type; |
| 308 | 22 | if (block) { |
| 309 | 4 | this.skip(i-1); |
| 310 | 4 | node.block = this.block(); |
| 311 | } | |
| 312 | 22 | return node; |
| 313 | }, | |
| 314 | ||
| 315 | /** | |
| 316 | * comment | |
| 317 | */ | |
| 318 | ||
| 319 | parseComment: function(){ | |
| 320 | 0 | var tok = this.expect('comment') |
| 321 | , node; | |
| 322 | ||
| 323 | 0 | if ('indent' == this.peek().type) { |
| 324 | 0 | node = new nodes.BlockComment(tok.val, this.block(), tok.buffer); |
| 325 | } else { | |
| 326 | 0 | node = new nodes.Comment(tok.val, tok.buffer); |
| 327 | } | |
| 328 | ||
| 329 | 0 | node.line = this.line(); |
| 330 | 0 | return node; |
| 331 | }, | |
| 332 | ||
| 333 | /** | |
| 334 | * doctype | |
| 335 | */ | |
| 336 | ||
| 337 | parseDoctype: function(){ | |
| 338 | 1 | var tok = this.expect('doctype') |
| 339 | , node = new nodes.Doctype(tok.val); | |
| 340 | 1 | node.line = this.line(); |
| 341 | 1 | return node; |
| 342 | }, | |
| 343 | ||
| 344 | /** | |
| 345 | * filter attrs? text-block | |
| 346 | */ | |
| 347 | ||
| 348 | parseFilter: function(){ | |
| 349 | 0 | var block |
| 350 | , tok = this.expect('filter') | |
| 351 | , attrs = this.accept('attrs'); | |
| 352 | ||
| 353 | 0 | this.lexer.pipeless = true; |
| 354 | 0 | block = this.parseTextBlock(); |
| 355 | 0 | this.lexer.pipeless = false; |
| 356 | ||
| 357 | 0 | var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); |
| 358 | 0 | node.line = this.line(); |
| 359 | 0 | return node; |
| 360 | }, | |
| 361 | ||
| 362 | /** | |
| 363 | * tag ':' attrs? block | |
| 364 | */ | |
| 365 | ||
| 366 | parseASTFilter: function(){ | |
| 367 | 0 | var block |
| 368 | , tok = this.expect('tag') | |
| 369 | , attrs = this.accept('attrs'); | |
| 370 | ||
| 371 | 0 | this.expect(':'); |
| 372 | 0 | block = this.block(); |
| 373 | ||
| 374 | 0 | var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); |
| 375 | 0 | node.line = this.line(); |
| 376 | 0 | return node; |
| 377 | }, | |
| 378 | ||
| 379 | /** | |
| 380 | * each block | |
| 381 | */ | |
| 382 | ||
| 383 | parseEach: function(){ | |
| 384 | 3 | var tok = this.expect('each') |
| 385 | , node = new nodes.Each(tok.code, tok.val, tok.key); | |
| 386 | 3 | node.line = this.line(); |
| 387 | 3 | node.block = this.block(); |
| 388 | 3 | return node; |
| 389 | }, | |
| 390 | ||
| 391 | /** | |
| 392 | * 'extends' name | |
| 393 | */ | |
| 394 | ||
| 395 | parseExtends: function(){ | |
| 396 | 0 | var path = require('path') |
| 397 | , fs = require('fs') | |
| 398 | , dirname = path.dirname | |
| 399 | , basename = path.basename | |
| 400 | , join = path.join; | |
| 401 | ||
| 402 | 0 | if (!this.filename) |
| 403 | 0 | throw new Error('the "filename" option is required to extend templates'); |
| 404 | ||
| 405 | 0 | var path = this.expect('extends').val.trim() |
| 406 | , dir = dirname(this.filename); | |
| 407 | ||
| 408 | 0 | var path = join(dir, path + '.jade') |
| 409 | , str = fs.readFileSync(path, 'utf8') | |
| 410 | , parser = new Parser(str, path, this.options); | |
| 411 | ||
| 412 | 0 | parser.blocks = this.blocks; |
| 413 | 0 | parser.contexts = this.contexts; |
| 414 | 0 | this.extending = parser; |
| 415 | ||
| 416 | // TODO: null node | |
| 417 | 0 | return new nodes.Literal(''); |
| 418 | }, | |
| 419 | ||
| 420 | /** | |
| 421 | * 'block' name block | |
| 422 | */ | |
| 423 | ||
| 424 | parseBlock: function(){ | |
| 425 | 0 | var block = this.expect('block') |
| 426 | , mode = block.mode | |
| 427 | , name = block.val.trim(); | |
| 428 | ||
| 429 | 0 | block = 'indent' == this.peek().type |
| 430 | ? this.block() | |
| 431 | : new nodes.Block(new nodes.Literal('')); | |
| 432 | ||
| 433 | 0 | var prev = this.blocks[name]; |
| 434 | ||
| 435 | 0 | if (prev) { |
| 436 | 0 | switch (prev.mode) { |
| 437 | case 'append': | |
| 438 | 0 | block.nodes = block.nodes.concat(prev.nodes); |
| 439 | 0 | prev = block; |
| 440 | 0 | break; |
| 441 | case 'prepend': | |
| 442 | 0 | block.nodes = prev.nodes.concat(block.nodes); |
| 443 | 0 | prev = block; |
| 444 | 0 | break; |
| 445 | } | |
| 446 | } | |
| 447 | ||
| 448 | 0 | block.mode = mode; |
| 449 | 0 | return this.blocks[name] = prev || block; |
| 450 | }, | |
| 451 | ||
| 452 | /** | |
| 453 | * include block? | |
| 454 | */ | |
| 455 | ||
| 456 | parseInclude: function(){ | |
| 457 | 3 | var path = require('path') |
| 458 | , fs = require('fs') | |
| 459 | , dirname = path.dirname | |
| 460 | , basename = path.basename | |
| 461 | , join = path.join; | |
| 462 | ||
| 463 | 3 | var path = this.expect('include').val.trim() |
| 464 | , dir = dirname(this.filename); | |
| 465 | ||
| 466 | 3 | if (!this.filename) |
| 467 | 0 | throw new Error('the "filename" option is required to use includes'); |
| 468 | ||
| 469 | // no extension | |
| 470 | 3 | if (!~basename(path).indexOf('.')) { |
| 471 | 1 | path += '.jade'; |
| 472 | } | |
| 473 | ||
| 474 | // non-jade | |
| 475 | 3 | if ('.jade' != path.substr(-5)) { |
| 476 | 2 | var path = join(dir, path) |
| 477 | , str = fs.readFileSync(path, 'utf8'); | |
| 478 | 2 | return new nodes.Literal(str); |
| 479 | } | |
| 480 | ||
| 481 | 1 | var path = join(dir, path) |
| 482 | , str = fs.readFileSync(path, 'utf8') | |
| 483 | , parser = new Parser(str, path, this.options); | |
| 484 | 1 | parser.blocks = this.blocks; |
| 485 | 1 | parser.mixins = this.mixins; |
| 486 | ||
| 487 | 1 | this.context(parser); |
| 488 | 1 | var ast = parser.parse(); |
| 489 | 1 | this.context(); |
| 490 | 1 | ast.filename = path; |
| 491 | ||
| 492 | 1 | if ('indent' == this.peek().type) { |
| 493 | 0 | ast.includeBlock().push(this.block()); |
| 494 | } | |
| 495 | ||
| 496 | 1 | return ast; |
| 497 | }, | |
| 498 | ||
| 499 | /** | |
| 500 | * call ident block | |
| 501 | */ | |
| 502 | ||
| 503 | parseCall: function(){ | |
| 504 | 0 | var tok = this.expect('call') |
| 505 | , name = tok.val | |
| 506 | , args = tok.args | |
| 507 | , mixin = new nodes.Mixin(name, args, new nodes.Block, true); | |
| 508 | ||
| 509 | 0 | this.tag(mixin); |
| 510 | 0 | if (mixin.block.isEmpty()) mixin.block = null; |
| 511 | 0 | return mixin; |
| 512 | }, | |
| 513 | ||
| 514 | /** | |
| 515 | * mixin block | |
| 516 | */ | |
| 517 | ||
| 518 | parseMixin: function(){ | |
| 519 | 0 | var tok = this.expect('mixin') |
| 520 | , name = tok.val | |
| 521 | , args = tok.args | |
| 522 | , mixin; | |
| 523 | ||
| 524 | // definition | |
| 525 | 0 | if ('indent' == this.peek().type) { |
| 526 | 0 | mixin = new nodes.Mixin(name, args, this.block(), false); |
| 527 | 0 | this.mixins[name] = mixin; |
| 528 | 0 | return mixin; |
| 529 | // call | |
| 530 | } else { | |
| 531 | 0 | return new nodes.Mixin(name, args, null, true); |
| 532 | } | |
| 533 | }, | |
| 534 | ||
| 535 | /** | |
| 536 | * indent (text | newline)* outdent | |
| 537 | */ | |
| 538 | ||
| 539 | parseTextBlock: function(){ | |
| 540 | 0 | var block = new nodes.Block; |
| 541 | 0 | block.line = this.line(); |
| 542 | 0 | var spaces = this.expect('indent').val; |
| 543 | 0 | if (null == this._spaces) this._spaces = spaces; |
| 544 | 0 | var indent = Array(spaces - this._spaces + 1).join(' '); |
| 545 | 0 | while ('outdent' != this.peek().type) { |
| 546 | 0 | switch (this.peek().type) { |
| 547 | case 'newline': | |
| 548 | 0 | this.advance(); |
| 549 | 0 | break; |
| 550 | case 'indent': | |
| 551 | 0 | this.parseTextBlock().nodes.forEach(function(node){ |
| 552 | 0 | block.push(node); |
| 553 | }); | |
| 554 | 0 | break; |
| 555 | default: | |
| 556 | 0 | var text = new nodes.Text(indent + this.advance().val); |
| 557 | 0 | text.line = this.line(); |
| 558 | 0 | block.push(text); |
| 559 | } | |
| 560 | } | |
| 561 | ||
| 562 | 0 | if (spaces == this._spaces) this._spaces = null; |
| 563 | 0 | this.expect('outdent'); |
| 564 | 0 | return block; |
| 565 | }, | |
| 566 | ||
| 567 | /** | |
| 568 | * indent expr* outdent | |
| 569 | */ | |
| 570 | ||
| 571 | block: function(){ | |
| 572 | 26 | var block = new nodes.Block; |
| 573 | 26 | block.line = this.line(); |
| 574 | 26 | this.expect('indent'); |
| 575 | 26 | while ('outdent' != this.peek().type) { |
| 576 | 61 | if ('newline' == this.peek().type) { |
| 577 | 4 | this.advance(); |
| 578 | } else { | |
| 579 | 57 | block.push(this.parseExpr()); |
| 580 | } | |
| 581 | } | |
| 582 | 26 | this.expect('outdent'); |
| 583 | 26 | return block; |
| 584 | }, | |
| 585 | ||
| 586 | /** | |
| 587 | * interpolation (attrs | class | id)* (text | code | ':')? newline* block? | |
| 588 | */ | |
| 589 | ||
| 590 | parseInterpolation: function(){ | |
| 591 | 0 | var tok = this.advance(); |
| 592 | 0 | var tag = new nodes.Tag(tok.val); |
| 593 | 0 | tag.buffer = true; |
| 594 | 0 | return this.tag(tag); |
| 595 | }, | |
| 596 | ||
| 597 | /** | |
| 598 | * tag (attrs | class | id)* (text | code | ':')? newline* block? | |
| 599 | */ | |
| 600 | ||
| 601 | parseTag: function(){ | |
| 602 | // ast-filter look-ahead | |
| 603 | 47 | var i = 2; |
| 604 | 50 | if ('attrs' == this.lookahead(i).type) ++i; |
| 605 | 47 | if (':' == this.lookahead(i).type) { |
| 606 | 0 | if ('indent' == this.lookahead(++i).type) { |
| 607 | 0 | return this.parseASTFilter(); |
| 608 | } | |
| 609 | } | |
| 610 | ||
| 611 | 47 | var tok = this.advance() |
| 612 | , tag = new nodes.Tag(tok.val); | |
| 613 | ||
| 614 | 47 | tag.selfClosing = tok.selfClosing; |
| 615 | ||
| 616 | 47 | return this.tag(tag); |
| 617 | }, | |
| 618 | ||
| 619 | /** | |
| 620 | * Parse tag. | |
| 621 | */ | |
| 622 | ||
| 623 | tag: function(tag){ | |
| 624 | 47 | var dot; |
| 625 | ||
| 626 | 47 | tag.line = this.line(); |
| 627 | ||
| 628 | // (attrs | class | id)* | |
| 629 | out: | |
| 630 | while (true) { | |
| 631 | 85 | switch (this.peek().type) { |
| 632 | case 'id': | |
| 633 | case 'class': | |
| 634 | 31 | var tok = this.advance(); |
| 635 | 31 | tag.setAttribute(tok.type, "'" + tok.val + "'"); |
| 636 | 31 | continue; |
| 637 | case 'attrs': | |
| 638 | 7 | var tok = this.advance() |
| 639 | , obj = tok.attrs | |
| 640 | , escaped = tok.escaped | |
| 641 | , names = Object.keys(obj); | |
| 642 | ||
| 643 | 7 | if (tok.selfClosing) tag.selfClosing = true; |
| 644 | ||
| 645 | 7 | for (var i = 0, len = names.length; i < len; ++i) { |
| 646 | 7 | var name = names[i] |
| 647 | , val = obj[name]; | |
| 648 | 7 | tag.setAttribute(name, val, escaped[name]); |
| 649 | } | |
| 650 | 7 | continue; |
| 651 | default: | |
| 652 | 47 | break out; |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | // check immediate '.' | |
| 657 | 47 | if ('.' == this.peek().val) { |
| 658 | 0 | dot = tag.textOnly = true; |
| 659 | 0 | this.advance(); |
| 660 | } | |
| 661 | ||
| 662 | // (text | code | ':')? | |
| 663 | 47 | switch (this.peek().type) { |
| 664 | case 'text': | |
| 665 | 11 | tag.block.push(this.parseText()); |
| 666 | 11 | break; |
| 667 | case 'code': | |
| 668 | 16 | tag.code = this.parseCode(); |
| 669 | 16 | break; |
| 670 | case ':': | |
| 671 | 0 | this.advance(); |
| 672 | 0 | tag.block = new nodes.Block; |
| 673 | 0 | tag.block.push(this.parseExpr()); |
| 674 | 0 | break; |
| 675 | } | |
| 676 | ||
| 677 | // newline* | |
| 678 | 65 | while ('newline' == this.peek().type) this.advance(); |
| 679 | ||
| 680 | 47 | tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name); |
| 681 | ||
| 682 | // script special-case | |
| 683 | 47 | if ('script' == tag.name) { |
| 684 | 0 | var type = tag.getAttribute('type'); |
| 685 | 0 | if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) { |
| 686 | 0 | tag.textOnly = false; |
| 687 | } | |
| 688 | } | |
| 689 | ||
| 690 | // block? | |
| 691 | 47 | if ('indent' == this.peek().type) { |
| 692 | 19 | if (tag.textOnly) { |
| 693 | 0 | this.lexer.pipeless = true; |
| 694 | 0 | tag.block = this.parseTextBlock(); |
| 695 | 0 | this.lexer.pipeless = false; |
| 696 | } else { | |
| 697 | 19 | var block = this.block(); |
| 698 | 19 | if (tag.block) { |
| 699 | 19 | for (var i = 0, len = block.nodes.length; i < len; ++i) { |
| 700 | 48 | tag.block.push(block.nodes[i]); |
| 701 | } | |
| 702 | } else { | |
| 703 | 0 | tag.block = block; |
| 704 | } | |
| 705 | } | |
| 706 | } | |
| 707 | ||
| 708 | 47 | return tag; |
| 709 | } | |
| 710 | }; | |
| 711 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - runtime | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Lame Array.isArray() polyfill for now. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | if (!Array.isArray) { |
| 13 | 0 | Array.isArray = function(arr){ |
| 14 | 0 | return '[object Array]' == Object.prototype.toString.call(arr); |
| 15 | }; | |
| 16 | } | |
| 17 | ||
| 18 | /** | |
| 19 | * Lame Object.keys() polyfill for now. | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | if (!Object.keys) { |
| 23 | 0 | Object.keys = function(obj){ |
| 24 | 0 | var arr = []; |
| 25 | 0 | for (var key in obj) { |
| 26 | 0 | if (obj.hasOwnProperty(key)) { |
| 27 | 0 | arr.push(key); |
| 28 | } | |
| 29 | } | |
| 30 | 0 | return arr; |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Merge two attribute objects giving precedence | |
| 36 | * to values in object `b`. Classes are special-cased | |
| 37 | * allowing for arrays and merging/joining appropriately | |
| 38 | * resulting in a string. | |
| 39 | * | |
| 40 | * @param {Object} a | |
| 41 | * @param {Object} b | |
| 42 | * @return {Object} a | |
| 43 | * @api private | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | exports.merge = function merge(a, b) { |
| 47 | 0 | var ac = a['class']; |
| 48 | 0 | var bc = b['class']; |
| 49 | ||
| 50 | 0 | if (ac || bc) { |
| 51 | 0 | ac = ac || []; |
| 52 | 0 | bc = bc || []; |
| 53 | 0 | if (!Array.isArray(ac)) ac = [ac]; |
| 54 | 0 | if (!Array.isArray(bc)) bc = [bc]; |
| 55 | 0 | ac = ac.filter(nulls); |
| 56 | 0 | bc = bc.filter(nulls); |
| 57 | 0 | a['class'] = ac.concat(bc).join(' '); |
| 58 | } | |
| 59 | ||
| 60 | 0 | for (var key in b) { |
| 61 | 0 | if (key != 'class') { |
| 62 | 0 | a[key] = b[key]; |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | 0 | return a; |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Filter null `val`s. | |
| 71 | * | |
| 72 | * @param {Mixed} val | |
| 73 | * @return {Mixed} | |
| 74 | * @api private | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | function nulls(val) { |
| 78 | 0 | return val != null; |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * Render the given attributes object. | |
| 83 | * | |
| 84 | * @param {Object} obj | |
| 85 | * @param {Object} escaped | |
| 86 | * @return {String} | |
| 87 | * @api private | |
| 88 | */ | |
| 89 | ||
| 90 | 1 | exports.attrs = function attrs(obj, escaped){ |
| 91 | 70 | var buf = [] |
| 92 | , terse = obj.terse; | |
| 93 | ||
| 94 | 70 | delete obj.terse; |
| 95 | 70 | var keys = Object.keys(obj) |
| 96 | , len = keys.length; | |
| 97 | ||
| 98 | 70 | if (len) { |
| 99 | 70 | buf.push(''); |
| 100 | 70 | for (var i = 0; i < len; ++i) { |
| 101 | 71 | var key = keys[i] |
| 102 | , val = obj[key]; | |
| 103 | ||
| 104 | 71 | if ('boolean' == typeof val || null == val) { |
| 105 | 0 | if (val) { |
| 106 | 0 | terse |
| 107 | ? buf.push(key) | |
| 108 | : buf.push(key + '="' + key + '"'); | |
| 109 | } | |
| 110 | 71 | } else if (0 == key.indexOf('data') && 'string' != typeof val) { |
| 111 | 0 | buf.push(key + "='" + JSON.stringify(val) + "'"); |
| 112 | 71 | } else if ('class' == key && Array.isArray(val)) { |
| 113 | 0 | buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); |
| 114 | 71 | } else if (escaped && escaped[key]) { |
| 115 | 2 | buf.push(key + '="' + exports.escape(val) + '"'); |
| 116 | } else { | |
| 117 | 69 | buf.push(key + '="' + val + '"'); |
| 118 | } | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | 70 | return buf.join(' '); |
| 123 | }; | |
| 124 | ||
| 125 | /** | |
| 126 | * Escape the given string of `html`. | |
| 127 | * | |
| 128 | * @param {String} html | |
| 129 | * @return {String} | |
| 130 | * @api private | |
| 131 | */ | |
| 132 | ||
| 133 | 1 | exports.escape = function escape(html){ |
| 134 | 2 | return String(html) |
| 135 | .replace(/&(?!(\w+|\#\d+);)/g, '&') | |
| 136 | .replace(/</g, '<') | |
| 137 | .replace(/>/g, '>') | |
| 138 | .replace(/"/g, '"'); | |
| 139 | }; | |
| 140 | ||
| 141 | /** | |
| 142 | * Re-throw the given `err` in context to the | |
| 143 | * the jade in `filename` at the given `lineno`. | |
| 144 | * | |
| 145 | * @param {Error} err | |
| 146 | * @param {String} filename | |
| 147 | * @param {String} lineno | |
| 148 | * @api private | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | exports.rethrow = function rethrow(err, filename, lineno){ |
| 152 | 0 | if (!filename) throw err; |
| 153 | ||
| 154 | 0 | var context = 3 |
| 155 | , str = require('fs').readFileSync(filename, 'utf8') | |
| 156 | , lines = str.split('\n') | |
| 157 | , start = Math.max(lineno - context, 0) | |
| 158 | , end = Math.min(lines.length, lineno + context); | |
| 159 | ||
| 160 | // Error context | |
| 161 | 0 | var context = lines.slice(start, end).map(function(line, i){ |
| 162 | 0 | var curr = i + start + 1; |
| 163 | 0 | return (curr == lineno ? ' > ' : ' ') |
| 164 | + curr | |
| 165 | + '| ' | |
| 166 | + line; | |
| 167 | }).join('\n'); | |
| 168 | ||
| 169 | // Alter exception message | |
| 170 | 0 | err.path = filename; |
| 171 | 0 | err.message = (filename || 'Jade') + ':' + lineno |
| 172 | + '\n' + context + '\n\n' + err.message; | |
| 173 | 0 | throw err; |
| 174 | }; | |
| 175 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - self closing tags | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | module.exports = [ |
| 9 | 'meta' | |
| 10 | , 'img' | |
| 11 | , 'link' | |
| 12 | , 'input' | |
| 13 | , 'source' | |
| 14 | , 'area' | |
| 15 | , 'base' | |
| 16 | , 'col' | |
| 17 | , 'br' | |
| 18 | , 'hr' | |
| 19 | ]; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Jade - utils | |
| 4 | * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> | |
| 5 | * MIT Licensed | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Convert interpolation in the given string to JavaScript. | |
| 10 | * | |
| 11 | * @param {String} str | |
| 12 | * @return {String} | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | var interpolate = exports.interpolate = function(str){ |
| 17 | 19 | return str.replace(/(\\)?([#!]){(.*?)}/g, function(str, escape, flag, code){ |
| 18 | 5 | return escape |
| 19 | ? str | |
| 20 | : "' + " | |
| 21 | + ('!' == flag ? '' : 'escape') | |
| 22 | + "((interp = " + code.replace(/\\'/g, "'") | |
| 23 | + ") == null ? '' : interp) + '"; | |
| 24 | }); | |
| 25 | }; | |
| 26 | ||
| 27 | /** | |
| 28 | * Escape single quotes in `str`. | |
| 29 | * | |
| 30 | * @param {String} str | |
| 31 | * @return {String} | |
| 32 | * @api private | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | var escape = exports.escape = function(str) { |
| 36 | 89 | return str.replace(/'/g, "\\'"); |
| 37 | }; | |
| 38 | ||
| 39 | /** | |
| 40 | * Interpolate, and escape the given `str`. | |
| 41 | * | |
| 42 | * @param {String} str | |
| 43 | * @return {String} | |
| 44 | * @api private | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | exports.text = function(str){ |
| 48 | 19 | return interpolate(escape(str)); |
| 49 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var logger = require('./Logger'); |
| 4 | 1 | var ObjectId = require('mongodb').BSONPure.ObjectID; |
| 5 | 1 | var _ = require('lodash'); |
| 6 | ||
| 7 | 1 | var db = require('./db'); |
| 8 | 1 | var utils = require('./utils'); |
| 9 | 1 | var filter = require('./options/Options'); |
| 10 | 1 | var validation = require('./validation/Validation'); |
| 11 | 1 | var operations = require('./operations/Operations'); |
| 12 | ||
| 13 | 1 | module.exports = Collection; |
| 14 | 1 | function Collection(mongoCollection, Model) { |
| 15 | 0 | var name = this.name = mongoCollection.name; |
| 16 | // var opts = this.opts = mongoCollection.opts; | |
| 17 | // var conn = this.conn = mongoCollection.conn; | |
| 18 | /*jshint -W064 *///new operator. | |
| 19 | 0 | var schema = Model().schema; |
| 20 | /*jshint -W106 *///Camel_Case | |
| 21 | 0 | var indexes = { |
| 22 | _id_ : [ [ '_id', 1 ] ] | |
| 23 | }; | |
| 24 | 0 | if (!db[name]) { |
| 25 | 0 | db[name] = {}; |
| 26 | } | |
| 27 | ||
| 28 | //------------------------------------------------------------------------- | |
| 29 | // | |
| 30 | // un implemented methods. If you need them why not contribute at | |
| 31 | // https://github.com/mccormicka/Mockgoose | |
| 32 | // | |
| 33 | //------------------------------------------------------------------------- | |
| 34 | ||
| 35 | ||
| 36 | 0 | this.ensureIndex = function (index, options, callback) { |
| 37 | 0 | logger.info('Ensure Index Called with arguments', arguments); |
| 38 | 0 | indexes[_.keys(index)[0] + '_1'] = _.pairs(index); |
| 39 | 0 | logger.info('Created indexes ', indexes); |
| 40 | 0 | callback(null, indexes); |
| 41 | }; | |
| 42 | ||
| 43 | 0 | this.getIndexes = function (callback) { |
| 44 | 0 | return callback(null, indexes); |
| 45 | }; | |
| 46 | ||
| 47 | 0 | this.mapReduce = function () { |
| 48 | 0 | throw new Error('Collection#mapReduce unimplemented by mockgoose!'); |
| 49 | }; | |
| 50 | ||
| 51 | 0 | this.save = function () { |
| 52 | //Save should not need to be implemented! as insert() should be called instead. | |
| 53 | 0 | throw new Error('Collection#save unimplemented by mockgoose!'); |
| 54 | }; | |
| 55 | ||
| 56 | //------------------------------------------------------------------------- | |
| 57 | // | |
| 58 | // Implemented drive methods. | |
| 59 | // | |
| 60 | //------------------------------------------------------------------------- | |
| 61 | ||
| 62 | 0 | this.findAndModify = function (conditions, sort, castedDoc, options, callback) { |
| 63 | 0 | if (options.remove) { |
| 64 | 0 | this.remove(conditions, options, callback); |
| 65 | } else { | |
| 66 | 0 | options.modify = true; |
| 67 | 0 | this.update(conditions, castedDoc, options, function (err, results) { |
| 68 | 0 | callback(err, results[0]); |
| 69 | }); | |
| 70 | } | |
| 71 | }; | |
| 72 | ||
| 73 | 0 | this.findOne = function (conditions, options, callback) { |
| 74 | 0 | this.find(conditions, options, function (err, result) { |
| 75 | 0 | if (err) { |
| 76 | 0 | return callback(err); |
| 77 | } | |
| 78 | 0 | result.toArray(function (err, results) { |
| 79 | 0 | callback(err, results[0]); |
| 80 | }); | |
| 81 | }); | |
| 82 | }; | |
| 83 | ||
| 84 | 0 | this.find = function (conditions, options, callback) { |
| 85 | 0 | var results; |
| 86 | 0 | var models = db[name]; |
| 87 | 0 | if (!_.isEmpty(conditions)) { |
| 88 | 0 | results = utils.findModelQuery(models, conditions); |
| 89 | } else { | |
| 90 | 0 | results = utils.objectToArray(utils.cloneItems(models)); |
| 91 | } | |
| 92 | 0 | results = filter.applyOptions(options, results); |
| 93 | ||
| 94 | 0 | if (results.name === 'MongoError') { |
| 95 | 0 | callback(results); |
| 96 | } else { | |
| 97 | 0 | var result = { |
| 98 | toArray: function (callback) { | |
| 99 | 0 | callback(null, results); |
| 100 | } | |
| 101 | }; | |
| 102 | 0 | callback(null, result); |
| 103 | } | |
| 104 | }; | |
| 105 | ||
| 106 | 0 | this.insert = function (obj, options, callback) { |
| 107 | 0 | if (!db[name]) { |
| 108 | 0 | db[name] = {}; |
| 109 | } | |
| 110 | 0 | validation.validate(name, obj, schema, function(err){ |
| 111 | 0 | if(err){ |
| 112 | 0 | return callback(err); |
| 113 | } | |
| 114 | 0 | db[name][obj._id] = obj; |
| 115 | 0 | logOptions(options); |
| 116 | 0 | callback(null, obj); |
| 117 | }); | |
| 118 | }; | |
| 119 | ||
| 120 | 0 | this.update = function (conditions, update, options, callback) { |
| 121 | 0 | logOptions(options); |
| 122 | 0 | var self = this; |
| 123 | 0 | this.find(conditions, options, function (err, results) { |
| 124 | 0 | if (err) { |
| 125 | 0 | return callback(err); |
| 126 | } | |
| 127 | 0 | results.toArray(function (err, results) { |
| 128 | 0 | if (!results.length) { |
| 129 | 0 | if (options.upsert) { |
| 130 | //Found no items. | |
| 131 | 0 | self.insert({ |
| 132 | _id: new ObjectId() | |
| 133 | }, options, function (err, result) { | |
| 134 | 0 | if (err) { |
| 135 | 0 | return callback(err); |
| 136 | } | |
| 137 | 0 | updateFoundItems(options, [result], update, callback); |
| 138 | }); | |
| 139 | } else { | |
| 140 | 0 | callback(null, options.midify ? [] : 0); |
| 141 | } | |
| 142 | } else { | |
| 143 | 0 | updateFoundItems(options, results, update, callback); |
| 144 | } | |
| 145 | }); | |
| 146 | }); | |
| 147 | }; | |
| 148 | ||
| 149 | 0 | this.remove = function (conditions, options, callback) { |
| 150 | 0 | this.find(conditions, options, function (err, results) { |
| 151 | 0 | if (err) { |
| 152 | 0 | return callback(err); |
| 153 | } | |
| 154 | 0 | results.toArray(function (err, results) { |
| 155 | 0 | if (options.remove) { |
| 156 | //If set to remove then only remove one item. | |
| 157 | 0 | if (results.length) { |
| 158 | 0 | results = results[0]; |
| 159 | 0 | delete db[name][results._id]; |
| 160 | } else { | |
| 161 | 0 | results = null; |
| 162 | } | |
| 163 | } else { | |
| 164 | 0 | _.each(results, function (result) { |
| 165 | 0 | delete db[name][result._id]; |
| 166 | }); | |
| 167 | } | |
| 168 | 0 | callback(null, results); |
| 169 | }); | |
| 170 | }); | |
| 171 | }; | |
| 172 | ||
| 173 | 0 | this.count = function (conditions, options, callback) { |
| 174 | 0 | this.find(conditions, options, function (err, results) { |
| 175 | 0 | if (err) { |
| 176 | 0 | return callback(err); |
| 177 | } | |
| 178 | 0 | results.toArray(function (err, results) { |
| 179 | 0 | callback(err, results.length); |
| 180 | }); | |
| 181 | }); | |
| 182 | }; | |
| 183 | ||
| 184 | ||
| 185 | //------------------------------------------------------------------------- | |
| 186 | // | |
| 187 | // Private Methods | |
| 188 | // | |
| 189 | //------------------------------------------------------------------------- | |
| 190 | ||
| 191 | 0 | function getOperation(type){ |
| 192 | 0 | return operations.getOperation(type); |
| 193 | } | |
| 194 | ||
| 195 | 0 | function logOptions(options) { |
| 196 | 0 | if (!_.isEmpty(options)) { |
| 197 | 0 | logger.debug('Options are', options); |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | 0 | function updateFoundItems(options, results, update, callback) { |
| 202 | 0 | if (!options.multi) { |
| 203 | 0 | results = [results[0]]; |
| 204 | } | |
| 205 | 0 | if (!options['new']) { |
| 206 | //Return original docs if new not specified. | |
| 207 | 0 | var originals = _.clone(results); |
| 208 | 0 | updateResults(results, update, options); |
| 209 | 0 | callback(null, options.modify ? originals : originals.length); |
| 210 | } else { | |
| 211 | //Return modified items | |
| 212 | 0 | updateResults(results, update, options); |
| 213 | 0 | callback(null, options.modify ? results : results.length); |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | 0 | function updateResults(results, update, options) { |
| 218 | 0 | _.each(results, function (result) { |
| 219 | 0 | updateResult(result, update, options); |
| 220 | 0 | db[name][result._id] = result; |
| 221 | }); | |
| 222 | } | |
| 223 | ||
| 224 | 0 | function updateResult(result, update, options) { |
| 225 | 0 | _.forIn(update, function (update, type) { |
| 226 | 0 | getOperation(type)(result, update, options); |
| 227 | }); | |
| 228 | } | |
| 229 | } | |
| 230 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var bunyan = require('bunyan'); |
| 4 | ||
| 5 | 1 | exports = module.exports = (function Logger(){ |
| 6 | 1 | return bunyan.createLogger({name:'Mockgoose'}); |
| 7 | })(); |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | 1 | var db = require('./db'); |
| 3 | ||
| 4 | 1 | var Collection = require('./Collection'); |
| 5 | ||
| 6 | //------------------------------------------------------------------------- | |
| 7 | // | |
| 8 | // Public Methods | |
| 9 | // | |
| 10 | //------------------------------------------------------------------------- | |
| 11 | ||
| 12 | 1 | module.exports = function (Model) { |
| 13 | ||
| 14 | /** | |
| 15 | * Mockgoose method to allow resetting of the db. | |
| 16 | * reset() will wipe the entire database. | |
| 17 | * reset('schema name') will remove all models associated with the schema. | |
| 18 | * @param type | |
| 19 | * @returns {boolean} | |
| 20 | */ | |
| 21 | 0 | Model.reset = function (type) { |
| 22 | 0 | return db.reset(type); |
| 23 | }; | |
| 24 | ||
| 25 | ||
| 26 | //------------------------------------------------------------------------- | |
| 27 | // | |
| 28 | // Private Methods that mimic the mongoose api overriding the default driver | |
| 29 | // | |
| 30 | //------------------------------------------------------------------------- | |
| 31 | ||
| 32 | 0 | Model.prototype.collection = new Collection(Model.prototype.collection, Model); |
| 33 | 0 | Model.collection = Model.prototype.collection; |
| 34 | ||
| 35 | 0 | return Model; |
| 36 | }; | |
| 37 | ||
| 38 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var models = {}; |
| 5 | 1 | module.exports = models; |
| 6 | ||
| 7 | 1 | module.exports.reset = reset; |
| 8 | ||
| 9 | 1 | function reset(type) { |
| 10 | 2 | if (!type) { |
| 11 | 2 | _.map(models, function(value, key){ |
| 12 | 2 | if(key !== 'reset'){ |
| 13 | 0 | delete models[key]; |
| 14 | } | |
| 15 | }); | |
| 16 | } else { | |
| 17 | 0 | delete models[type]; |
| 18 | } | |
| 19 | 2 | return true; |
| 20 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var logger = require('../Logger'); |
| 4 | 1 | var _ = require('lodash'); |
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function addToSetOperation(model, update, options) { |
| 7 | 0 | _.forIn(update, function (value, key) { |
| 8 | 0 | if(_.isObject(value) && value.$each){ |
| 9 | 0 | var obj = {}; |
| 10 | 0 | obj[key] = value.$each; |
| 11 | 0 | addToSetOperation(model, obj, options); |
| 12 | }else{ | |
| 13 | 0 | var original = model[key]; |
| 14 | 0 | if (_.isArray(original)) { |
| 15 | 0 | if (!_.isArray(value)) { |
| 16 | 0 | value = [value]; |
| 17 | } | |
| 18 | 0 | model[key] = _.union(original, value); |
| 19 | } else { | |
| 20 | 0 | logger.warn('$addToSet model value not an array', key); |
| 21 | } | |
| 22 | } | |
| 23 | }); | |
| 24 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var operations = require('./Operations'); |
| 5 | ||
| 6 | /*jshint -W098 *///options | |
| 7 | 1 | module.exports = function operation(model, update, options) { |
| 8 | 0 | var results = _.every(update.$all, function (value) { |
| 9 | 0 | if(value.$elemMatch){ |
| 10 | 0 | return operations.getOperation('$elemMatch')(model, value, options); |
| 11 | } | |
| 12 | 0 | return _.contains(model[options.queryItem], value); |
| 13 | }); | |
| 14 | 0 | return results; |
| 15 | }; | |
| 16 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var operations = require('./Operations'); |
| 5 | ||
| 6 | /*jshint -W098 *///options | |
| 7 | 1 | module.exports = function operation(model, update, options) { |
| 8 | 0 | var result = _.find(model[options.queryItem] || [model], function (item) { |
| 9 | 0 | return _.every(_.keys(update.$elemMatch), function (key) { |
| 10 | 0 | var value = update.$elemMatch[key]; |
| 11 | 0 | if(operations.isOperation(value)){ |
| 12 | 0 | return operations.getOperation(value)(item, value, {queryItem:key}); |
| 13 | } | |
| 14 | 0 | var equal = _.isEqual(item[key], value); |
| 15 | 0 | return equal; |
| 16 | }); | |
| 17 | }); | |
| 18 | ||
| 19 | 0 | return result; |
| 20 | }; | |
| 21 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | ||
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function operation(model, update, options) { |
| 7 | 0 | return model[options.queryItem] > update.$gt; |
| 8 | }; | |
| 9 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var utils = require('../utils'); |
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function inOperation(model, update, options) { |
| 7 | 0 | var modelValue = utils.findProperty(model, options.queryItem); |
| 8 | 0 | var contains = utils.contains; |
| 9 | 0 | return !!_.find(update.$in, function (value) { |
| 10 | 0 | if(contains(modelValue, value)){ |
| 11 | 0 | return true; |
| 12 | } | |
| 13 | 0 | return false; |
| 14 | }); | |
| 15 | ||
| 16 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | /*jshint -W098 *///options | |
| 5 | 1 | module.exports = function incOperation(model, update, options) { |
| 6 | 0 | _.forIn(update, function (value, key) { |
| 7 | 0 | model[key] += value; |
| 8 | }); | |
| 9 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var inOperation = require('./InOperation'); |
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function operation(model, update, options) { |
| 7 | 0 | var updateClone = _.defaults({}, update); |
| 8 | 0 | delete updateClone.$ne; |
| 9 | 0 | updateClone.$in = [update.$ne]; |
| 10 | 0 | return !inOperation(model , updateClone, options); |
| 11 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var logger = require('../Logger'); |
| 4 | 1 | var _ = require('lodash'); |
| 5 | ||
| 6 | 1 | var operations = { |
| 7 | '$pull': require('./PullOperation'), | |
| 8 | '$pullAll': require('./PullAllOperation'), | |
| 9 | '$push': require('./PushOperation'), | |
| 10 | '$pushAll': require('./PushAllOperation'), | |
| 11 | '$set': require('./SetOperation'), | |
| 12 | '$inc': require('./IncOperation'), | |
| 13 | '$addToSet' : require('./AddToSetOperation'), | |
| 14 | '$in' : require('./InOperation'), | |
| 15 | '$ne' : require('./NEOperation'), | |
| 16 | '$elemMatch' : require('./ElemMatchOperation'), | |
| 17 | '$all' : require('./AllOperation'), | |
| 18 | '$gt' : require('./GreaterThanOperation'), | |
| 19 | '$unset' : require('./UnsetOperation') | |
| 20 | }; | |
| 21 | ||
| 22 | 1 | module.exports.isOperation = function(value){ |
| 23 | 0 | value = module.exports.getOperationFromObject(value); |
| 24 | 0 | return !!operations[value]; |
| 25 | }; | |
| 26 | ||
| 27 | 1 | module.exports.getOperationFromObject = function(value){ |
| 28 | 0 | if(_.isObject(value)){ |
| 29 | 0 | return _.find(_.keys(operations), function(operation){ |
| 30 | 0 | return value[operation]; |
| 31 | }); | |
| 32 | } | |
| 33 | 0 | return value; |
| 34 | }; | |
| 35 | ||
| 36 | 1 | module.exports.getOperation = function getOperation(type){ |
| 37 | 0 | var operation = module.exports.getOperationFromObject(type); |
| 38 | 0 | if(!operations[operation]){ |
| 39 | 0 | logger.error('Mockgoose currently does not support the ' + operation + ' operation' ); |
| 40 | 0 | return false; |
| 41 | } | |
| 42 | 0 | return operations[operation]; |
| 43 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var pull = require('./PullOperation'); |
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function pullAllOperation(model, updates, options) { |
| 7 | 0 | _.forIn(updates, function (value, key) { |
| 8 | 0 | _.forEach(value, function(item){ |
| 9 | 0 | var update = {}; |
| 10 | 0 | update[key] = item; |
| 11 | 0 | pull(model, update, options); |
| 12 | }); | |
| 13 | }); | |
| 14 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var utils = require('../utils'); |
| 4 | 1 | var _ = require('lodash'); |
| 5 | ||
| 6 | /*jshint -W098 *///options | |
| 7 | 1 | module.exports = function pullOperation(model, update, options) { |
| 8 | 0 | pullItems(model, update); |
| 9 | }; | |
| 10 | ||
| 11 | //------------------------------------------------------------------------- | |
| 12 | // | |
| 13 | // Private Methods | |
| 14 | // | |
| 15 | //------------------------------------------------------------------------- | |
| 16 | ||
| 17 | 1 | function pullItems(model, pulls) { |
| 18 | 0 | for (var pull in pulls) { |
| 19 | 0 | if (pulls.hasOwnProperty(pull)) { |
| 20 | 0 | var values = model[pull]; |
| 21 | 0 | var match = utils.findMatch(values, pulls[pull]); |
| 22 | 0 | if (match.length > 0) { |
| 23 | 0 | pullItem(match, values); |
| 24 | } | |
| 25 | } | |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | 1 | function pullItem(match, values) { |
| 30 | 0 | for (var i in match) { |
| 31 | 0 | if (match.hasOwnProperty(i)) { |
| 32 | 0 | var index = indexOf(values, match[i]); |
| 33 | 0 | if (index > -1) { |
| 34 | 0 | values.splice(index, 1); |
| 35 | } | |
| 36 | } | |
| 37 | } | |
| 38 | } | |
| 39 | ||
| 40 | 1 | function indexOf(values, match) { |
| 41 | 0 | var index = -1; |
| 42 | 0 | var foundItem = _.find(values, function (value) { |
| 43 | 0 | index++; |
| 44 | 0 | return deepEquals(value, match); |
| 45 | }); | |
| 46 | 0 | if (foundItem) { |
| 47 | 0 | return index; |
| 48 | } | |
| 49 | 0 | return -1; |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Implementation from | |
| 54 | * http://stackoverflow.com/questions/1068834/object-comparison-in-javascript | |
| 55 | */ | |
| 56 | 1 | function deepEquals(x, y) { |
| 57 | 0 | if (x === y) { |
| 58 | 0 | return true; |
| 59 | } | |
| 60 | // if both x and y are null or undefined and exactly the same | |
| 61 | ||
| 62 | 0 | if (!( x instanceof Object ) || !( y instanceof Object )) { |
| 63 | 0 | return false; |
| 64 | ||
| 65 | } | |
| 66 | // if they are not strictly equal, they both need to be Objects | |
| 67 | ||
| 68 | 0 | if (x.constructor !== y.constructor) { |
| 69 | 0 | return false; |
| 70 | } | |
| 71 | // they must have the exact same prototype chain, the closest we can do is | |
| 72 | // test there constructor. | |
| 73 | ||
| 74 | 0 | for (var p in x) { |
| 75 | 0 | if (!x.hasOwnProperty(p)) { |
| 76 | 0 | continue; |
| 77 | } | |
| 78 | // other properties were tested using x.constructor === y.constructor | |
| 79 | ||
| 80 | 0 | if (!y.hasOwnProperty(p)) { |
| 81 | 0 | return false; |
| 82 | } | |
| 83 | // allows to compare x[ p ] and y[ p ] when set to undefined | |
| 84 | ||
| 85 | 0 | if (x[ p ] === y[ p ]) { |
| 86 | 0 | continue; |
| 87 | } | |
| 88 | // if they have the same strict value or identity then they are equal | |
| 89 | ||
| 90 | 0 | if (typeof( x[ p ] ) !== 'object') { |
| 91 | 0 | return false; |
| 92 | } | |
| 93 | // Numbers, Strings, Functions, Booleans must be strictly equal | |
| 94 | ||
| 95 | 0 | if (!deepEquals(x[ p ], y[ p ])) { |
| 96 | 0 | return false; |
| 97 | } | |
| 98 | // Objects and Arrays must be tested recursively | |
| 99 | } | |
| 100 | ||
| 101 | 0 | for (p in y) { |
| 102 | 0 | if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) { |
| 103 | 0 | return false; |
| 104 | } | |
| 105 | // allows x[ p ] to be set to undefined | |
| 106 | } | |
| 107 | 0 | return true; |
| 108 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var push = require('./PushOperation'); |
| 5 | /*jshint -W098 *///options | |
| 6 | 1 | module.exports = function pushAllOperation(model, updates, options) { |
| 7 | 0 | _.forIn(updates, function (value, key) { |
| 8 | 0 | _.forEach(value, function(item){ |
| 9 | 0 | var update = {}; |
| 10 | 0 | update[key] = item; |
| 11 | 0 | push(model, update, options); |
| 12 | }); | |
| 13 | }); | |
| 14 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | /*jshint -W098 *///options | |
| 5 | 1 | module.exports = function pushOperation(model, update, options) { |
| 6 | 0 | _.forIn(update, function (value, key) { |
| 7 | 0 | var temp = []; |
| 8 | 0 | if (model[key]) { |
| 9 | 0 | temp = temp.concat(model[key]); |
| 10 | } | |
| 11 | 0 | var updates = value; |
| 12 | 0 | if (updates.$each) { |
| 13 | 0 | temp = temp.concat(updates.$each); |
| 14 | } else { | |
| 15 | 0 | temp.push(updates); |
| 16 | } | |
| 17 | 0 | model[key] = temp; |
| 18 | }); | |
| 19 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | /*jshint -W098 *///options | |
| 5 | 1 | module.exports = function setOperation(model, update, options) { |
| 6 | 0 | _.forIn(update, function (value, key) { |
| 7 | 0 | model[key] = value; |
| 8 | }); | |
| 9 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | /*jshint -W098 *///options | |
| 5 | 1 | module.exports = function unsetOperation(model, update, options) { |
| 6 | 0 | _.forIn(update, function (value, key) { |
| 7 | 0 | delete model[key]; |
| 8 | }); | |
| 9 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var mask = require('mongoosemask'); |
| 5 | ||
| 6 | 1 | module.exports = function filterFields(fields, results) { |
| 7 | 0 | var includes = []; |
| 8 | 0 | var excludes = []; |
| 9 | 0 | if (fields) { |
| 10 | 0 | if (_.isObject(fields)) { |
| 11 | 0 | _.forIn(fields, function (value, key) { |
| 12 | 0 | if (!!value ) { |
| 13 | 0 | includes.push(key); |
| 14 | } else { | |
| 15 | 0 | excludes.push(key); |
| 16 | } | |
| 17 | }); | |
| 18 | 0 | } else if (_.isString(fields)) { |
| 19 | 0 | _.each(fields.split(' '), function (field) { |
| 20 | 0 | if (field.indexOf('-') > -1) { |
| 21 | 0 | excludes.push(field.slice(1)); |
| 22 | } else { | |
| 23 | 0 | includes.push(field); |
| 24 | } | |
| 25 | }); | |
| 26 | } else { | |
| 27 | 0 | throw new Error('Fields must be a string or an object!'); |
| 28 | } | |
| 29 | } | |
| 30 | 0 | if(includes.length && excludes.length ){ |
| 31 | 0 | var error = new Error('You cannot currently mix including and excluding fields. Contact us if this is an issue.'); |
| 32 | 0 | error.name = 'MongoError'; |
| 33 | 0 | return error; |
| 34 | } | |
| 35 | ||
| 36 | 0 | if (includes.length || excludes.length) { |
| 37 | 0 | results = _.map(results, function (result) { |
| 38 | 0 | if (includes.length) { |
| 39 | 0 | result = mask.expose(result, includes); |
| 40 | } | |
| 41 | 0 | if (excludes.length) { |
| 42 | 0 | result = mask.mask(result, excludes); |
| 43 | } | |
| 44 | 0 | return result; |
| 45 | }); | |
| 46 | } | |
| 47 | 0 | return results; |
| 48 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | 1 | var filter = require('./Filter'); |
| 5 | 1 | var sort = require('./Sort'); |
| 6 | 1 | var operations = require('../operations/Operations'); |
| 7 | ||
| 8 | 1 | module.exports.applyOptions = function applyOptions(options, items) { |
| 9 | 0 | if (!_.isNull(options) && !_.isUndefined(options)) { |
| 10 | 0 | items = sort(options.sort, items); |
| 11 | 0 | items = filter(options.fields, items); |
| 12 | 0 | items = applyOperations(options.fields, items); |
| 13 | } | |
| 14 | 0 | return items; |
| 15 | }; | |
| 16 | ||
| 17 | 1 | function applyOperations(fields, items) { |
| 18 | 0 | if (_.isObject(fields)) { |
| 19 | 0 | _.each(items, function (item) { |
| 20 | 0 | _.each(_.keys(fields), function (key) { |
| 21 | 0 | var value = fields[key]; |
| 22 | 0 | if (operations.isOperation(value)) { |
| 23 | 0 | item[key] = _.find(item[key], function (element) { |
| 24 | 0 | return operations.getOperation(value)(element, value, {queryItem: key}); |
| 25 | }); | |
| 26 | } | |
| 27 | }); | |
| 28 | }); | |
| 29 | } | |
| 30 | 0 | return items; |
| 31 | } | |
| 32 | ||
| 33 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var _ = require('lodash'); |
| 4 | ||
| 5 | 1 | module.exports = function sortItems(sort, items) { |
| 6 | 0 | var item = items[0]; |
| 7 | 0 | if (!_.isUndefined(item) && !_.isUndefined(sort)) { |
| 8 | 0 | _.forIn(sort, function (value, key) { |
| 9 | 0 | if (item[key]) { |
| 10 | 0 | if (_.isNumber(item[key])) { |
| 11 | 0 | sortNumeric(value, items, key); |
| 12 | } | |
| 13 | 0 | else if(item[key] instanceof Date) { |
| 14 | 0 | sortDate(value, items, key); |
| 15 | } | |
| 16 | else { | |
| 17 | 0 | sortAlpha(value, items, key); |
| 18 | } | |
| 19 | } | |
| 20 | }); | |
| 21 | } | |
| 22 | 0 | return items; |
| 23 | }; | |
| 24 | ||
| 25 | //------------------------------------------------------------------------- | |
| 26 | // | |
| 27 | // Private Methods | |
| 28 | // | |
| 29 | //------------------------------------------------------------------------- | |
| 30 | ||
| 31 | 1 | function sortDate(value, items, key){ |
| 32 | 0 | return sortNumeric(value, items, key); |
| 33 | } | |
| 34 | ||
| 35 | 1 | function sortNumeric(value, items, key) { |
| 36 | 0 | if (value === 1) { |
| 37 | 0 | items.sort(function (a, b) { |
| 38 | 0 | return a[key] - b[key]; |
| 39 | }); | |
| 40 | } else { | |
| 41 | 0 | items.sort(function (a, b) { |
| 42 | 0 | return b[key] - a[key]; |
| 43 | }); | |
| 44 | } | |
| 45 | } | |
| 46 | ||
| 47 | 1 | function sortAlpha(value, items, key) { |
| 48 | 0 | if (value === 1) { |
| 49 | 0 | items.sort(function (a, b) { |
| 50 | 0 | return a[key].localeCompare(b[key]); |
| 51 | }); | |
| 52 | } else { | |
| 53 | 0 | items.sort(function (a, b) { |
| 54 | 0 | return b[key].localeCompare(a[key]); |
| 55 | }); | |
| 56 | } | |
| 57 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | 1 | var _ = require('lodash'); |
| 3 | 1 | var operations = require('./operations/Operations'); |
| 4 | ||
| 5 | 1 | module.exports.isEmpty = _.isEmpty; |
| 6 | 1 | module.exports.objectToArray = objectToArray; |
| 7 | 1 | module.exports.cloneItems = cloneItems; |
| 8 | 1 | module.exports.cloneItem = cloneItem; |
| 9 | 1 | module.exports.matchParams = matchParams; |
| 10 | 1 | module.exports.allParamsMatch = allParamsMatch; |
| 11 | 1 | module.exports.buildResults = buildResults; |
| 12 | 1 | module.exports.findMatch = findMatch; |
| 13 | 1 | module.exports.foundModel = foundModel; |
| 14 | 1 | module.exports.findModelQuery = findModelQuery; |
| 15 | 1 | module.exports.findProperty = findProperty; |
| 16 | 1 | module.exports.contains = contains; |
| 17 | ||
| 18 | //------------------------------------------------------------------------- | |
| 19 | // | |
| 20 | // Private Methods | |
| 21 | // | |
| 22 | //------------------------------------------------------------------------- | |
| 23 | ||
| 24 | 1 | function findModelQuery(items, query) { |
| 25 | 0 | return findMatch(items, query); |
| 26 | } | |
| 27 | ||
| 28 | 1 | function findMatch(items, query) { |
| 29 | 0 | var results = {}; |
| 30 | 0 | for (var key in items) { |
| 31 | 0 | if (items.hasOwnProperty(key)) { |
| 32 | 0 | for (var q in query) { |
| 33 | 0 | buildResults(query, q, items, key, results); |
| 34 | } | |
| 35 | } | |
| 36 | } | |
| 37 | 0 | return objectToArray(cloneItems(results)); |
| 38 | } | |
| 39 | ||
| 40 | 1 | function buildResults(query, q, items, key, results) { |
| 41 | 0 | if (query.hasOwnProperty(q)) { |
| 42 | 0 | var item = foundModel(items[key], query, q); |
| 43 | 0 | if (item) { |
| 44 | 0 | if (item._id) { |
| 45 | 0 | results[item._id] = item; |
| 46 | } else { | |
| 47 | 0 | results[Math.random()] = item; |
| 48 | } | |
| 49 | } | |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | 1 | function foundModel(item, query, q) { |
| 54 | 0 | if (matchParams(item, query, q)) { |
| 55 | 0 | var allMatch = allParamsMatch(query, item); |
| 56 | 0 | if (allMatch) { |
| 57 | 0 | return item;//return cloneItem(item); |
| 58 | } | |
| 59 | } | |
| 60 | 0 | return false; |
| 61 | } | |
| 62 | ||
| 63 | 1 | function contains(obj, target) { |
| 64 | 0 | if (!obj) { |
| 65 | 0 | return false; |
| 66 | } | |
| 67 | 0 | if (typeof obj === 'object') { |
| 68 | 0 | obj = JSON.stringify(obj); |
| 69 | } | |
| 70 | 0 | return obj.indexOf(target) !== -1; |
| 71 | } | |
| 72 | ||
| 73 | 1 | function allParamsMatch(query, item) { |
| 74 | 0 | var allMatch = true; |
| 75 | 0 | for (var qq in query) { |
| 76 | 0 | if (query.hasOwnProperty(qq)) { |
| 77 | 0 | if (!matchParams(item, query, qq)) { |
| 78 | 0 | allMatch = false; |
| 79 | 0 | break; |
| 80 | } | |
| 81 | } | |
| 82 | } | |
| 83 | 0 | return allMatch; |
| 84 | } | |
| 85 | ||
| 86 | 1 | function findValue(objectPath, obj) { |
| 87 | 0 | var objPath = objectPath.split('.'); |
| 88 | //Find our value. | |
| 89 | 0 | var value = objectPath; |
| 90 | 0 | for (var k = 0; k < objPath.length; k++) { |
| 91 | 0 | value = obj[objPath[k]]; |
| 92 | 0 | obj = value; |
| 93 | 0 | if (_.isUndefined(obj)) { |
| 94 | 0 | break; |
| 95 | 0 | } else if(_.isArray(obj)){ |
| 96 | 0 | var values = _.pluck(obj, objPath[k+1]); |
| 97 | 0 | if(!_.isUndefined(values[0])){ |
| 98 | 0 | value = values; |
| 99 | 0 | break; |
| 100 | } | |
| 101 | } | |
| 102 | } | |
| 103 | 0 | return value; |
| 104 | } | |
| 105 | ||
| 106 | 1 | function matchItems(value, queryItem, query, q, item) { |
| 107 | 0 | var result = matchValues(value, queryItem); |
| 108 | 0 | if (!result) { |
| 109 | 0 | result = matchObjectParams(query, q, item); |
| 110 | } | |
| 111 | 0 | if (!result) { |
| 112 | 0 | result = matchArrayParams(value, queryItem, query, q, item); |
| 113 | } | |
| 114 | 0 | return result; |
| 115 | } | |
| 116 | ||
| 117 | 1 | function matchParams(item, query, q) { |
| 118 | 0 | var value = findValue(q, item); |
| 119 | 0 | var queryItem = query[q]; |
| 120 | 0 | var result = matchItems(value, queryItem, query, q, item); |
| 121 | 0 | return result; |
| 122 | } | |
| 123 | ||
| 124 | 1 | function matchValues(item, value) { |
| 125 | 0 | return _.isEqual(item,value); |
| 126 | } | |
| 127 | ||
| 128 | 1 | function findProperty(item, q){ |
| 129 | 0 | var values = q.split('.'); |
| 130 | 0 | var value = item; |
| 131 | 0 | _.each(values, function(prop){ |
| 132 | 0 | value = value[prop]; |
| 133 | }); | |
| 134 | 0 | return value; |
| 135 | } | |
| 136 | ||
| 137 | 1 | function matchObjectParams(query, q, item) { |
| 138 | 0 | var result = false; |
| 139 | 0 | if (typeof query[q] === 'object') { |
| 140 | 0 | _.each(_.keys(query[q]), function(key){ |
| 141 | 0 | if(operations.isOperation(key)){ |
| 142 | 0 | result = operations.getOperation(key)(result || item, query[q], {query:query, queryItem:q}); |
| 143 | 0 | }else if(operations.isOperation(query[q][key])){ |
| 144 | 0 | result = operations.getOperation(query[q][key])(result || item, query[q][key], {query:query, queryItem:key}); |
| 145 | 0 | }else if (item[q] && query[q]) { |
| 146 | 0 | if (item[q].toString() === query[q].toString()) { |
| 147 | 0 | result = true; |
| 148 | } | |
| 149 | } | |
| 150 | }); | |
| 151 | } | |
| 152 | 0 | return result; |
| 153 | } | |
| 154 | ||
| 155 | 1 | function matchArrayParams(items, queryItem, query, q){ |
| 156 | 0 | if(_.isArray(items)){ |
| 157 | 0 | return _.any(items, function(item){ |
| 158 | 0 | return matchItems(item, queryItem, query, q, item); |
| 159 | }); | |
| 160 | } | |
| 161 | 0 | return false; |
| 162 | } | |
| 163 | ||
| 164 | 1 | function objectToArray(items) { |
| 165 | 0 | var results = []; |
| 166 | 0 | for (var model in items) { |
| 167 | 0 | results.push(items[model]); |
| 168 | } | |
| 169 | 0 | return results; |
| 170 | } | |
| 171 | ||
| 172 | 1 | function cloneItems(items) { |
| 173 | 0 | var clones = {}; |
| 174 | 0 | for (var item in items) { |
| 175 | 0 | clones[item] = cloneItem(items[item]); |
| 176 | } | |
| 177 | 0 | return clones; |
| 178 | } | |
| 179 | ||
| 180 | 1 | function cloneItem(item) { |
| 181 | 0 | return _.clone(item); |
| 182 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var db = require('./../db'); |
| 4 | 1 | var util = require('util'); |
| 5 | 1 | var utils = require('./../utils'); |
| 6 | 1 | var _ = require('lodash'); |
| 7 | ||
| 8 | /** | |
| 9 | * Duplicate path validation. | |
| 10 | * @type {Function} | |
| 11 | */ | |
| 12 | 1 | module.exports.validate = validateOptions; |
| 13 | ||
| 14 | ||
| 15 | 1 | function validatePath(doc, schema, pathName, type, error) { |
| 16 | 0 | var path = schema.paths[pathName]; |
| 17 | 0 | if (path.options.unique) { |
| 18 | 0 | var query = {}; |
| 19 | 0 | query[pathName] = doc[pathName]; |
| 20 | 0 | var results = utils.findModelQuery(db[type], query); |
| 21 | 0 | if (results.length > 0) { |
| 22 | 0 | var errMessage = util.format('E11000 duplicate key error index: %s', pathName); |
| 23 | 0 | var code = 11000; |
| 24 | 0 | error = new Error(errMessage, code); |
| 25 | 0 | error.name = 'MongoError'; |
| 26 | 0 | error.code = code; |
| 27 | 0 | return error; |
| 28 | } | |
| 29 | } | |
| 30 | 0 | return error; |
| 31 | } | |
| 32 | ||
| 33 | 1 | function validateOptions(type, doc, schema, cb) { |
| 34 | 0 | var error = null; |
| 35 | 0 | if (!db[type][doc._id.toString()]) { |
| 36 | 0 | _.forIn(schema.paths, function(value, pathName){ |
| 37 | 0 | error = validatePath(doc, schema, pathName, type, error); |
| 38 | }); | |
| 39 | } | |
| 40 | 0 | cb(error); |
| 41 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * Copyright (c) 2013 Trent Mick. All rights reserved. | |
| 3 | * | |
| 4 | * The bunyan logging library for node.js. | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var VERSION = '0.22.1'; |
| 8 | ||
| 9 | // Bunyan log format version. This becomes the 'v' field on all log records. | |
| 10 | // `0` is until I release a version '1.0.0' of node-bunyan. Thereafter, | |
| 11 | // starting with `1`, this will be incremented if there is any backward | |
| 12 | // incompatible change to the log record format. Details will be in | |
| 13 | // 'CHANGES.md' (the change log). | |
| 14 | 1 | var LOG_VERSION = 0; |
| 15 | ||
| 16 | ||
| 17 | 1 | var xxx = function xxx(s) { // internal dev/debug logging |
| 18 | 0 | var args = ['XX' + 'X: '+s].concat( |
| 19 | Array.prototype.slice.call(arguments, 1)); | |
| 20 | 0 | console.error.apply(this, args); |
| 21 | }; | |
| 22 | 1 | var xxx = function xxx() {}; // comment out to turn on debug logging |
| 23 | ||
| 24 | ||
| 25 | 1 | var os = require('os'); |
| 26 | 1 | var fs = require('fs'); |
| 27 | 1 | var util = require('util'); |
| 28 | 1 | var assert = require('assert'); |
| 29 | 1 | try { |
| 30 | 1 | var dtrace = require('dtrace-provider'); |
| 31 | } catch (e) { | |
| 32 | 0 | dtrace = null; |
| 33 | } | |
| 34 | 1 | var EventEmitter = require('events').EventEmitter; |
| 35 | ||
| 36 | // The 'mv' module is required for rotating-file stream support. | |
| 37 | 1 | try { |
| 38 | 1 | var mv = require('mv'); |
| 39 | } catch (e) { | |
| 40 | 0 | mv = null; |
| 41 | } | |
| 42 | ||
| 43 | ||
| 44 | ||
| 45 | //---- Internal support stuff | |
| 46 | ||
| 47 | 1 | function objCopy(obj) { |
| 48 | 3 | if (obj === null) { |
| 49 | 0 | return null; |
| 50 | 3 | } else if (Array.isArray(obj)) { |
| 51 | 0 | return obj.slice(); |
| 52 | } else { | |
| 53 | 3 | var copy = {}; |
| 54 | 3 | Object.keys(obj).forEach(function (k) { |
| 55 | 8 | copy[k] = obj[k]; |
| 56 | }); | |
| 57 | 3 | return copy; |
| 58 | } | |
| 59 | } | |
| 60 | ||
| 61 | 1 | var format = util.format; |
| 62 | 1 | if (!format) { |
| 63 | // If node < 0.6, then use its `util.format`: | |
| 64 | // <https://github.com/joyent/node/blob/master/lib/util.js#L22>: | |
| 65 | 0 | var inspect = util.inspect; |
| 66 | 0 | var formatRegExp = /%[sdj%]/g; |
| 67 | 0 | format = function format(f) { |
| 68 | 0 | if (typeof (f) !== 'string') { |
| 69 | 0 | var objects = []; |
| 70 | 0 | for (var i = 0; i < arguments.length; i++) { |
| 71 | 0 | objects.push(inspect(arguments[i])); |
| 72 | } | |
| 73 | 0 | return objects.join(' '); |
| 74 | } | |
| 75 | ||
| 76 | 0 | var i = 1; |
| 77 | 0 | var args = arguments; |
| 78 | 0 | var len = args.length; |
| 79 | 0 | var str = String(f).replace(formatRegExp, function (x) { |
| 80 | 0 | if (i >= len) |
| 81 | 0 | return x; |
| 82 | 0 | switch (x) { |
| 83 | 0 | case '%s': return String(args[i++]); |
| 84 | 0 | case '%d': return Number(args[i++]); |
| 85 | 0 | case '%j': return JSON.stringify(args[i++], safeCycles()); |
| 86 | 0 | case '%%': return '%'; |
| 87 | default: | |
| 88 | 0 | return x; |
| 89 | } | |
| 90 | }); | |
| 91 | 0 | for (var x = args[i]; i < len; x = args[++i]) { |
| 92 | 0 | if (x === null || typeof (x) !== 'object') { |
| 93 | 0 | str += ' ' + x; |
| 94 | } else { | |
| 95 | 0 | str += ' ' + inspect(x); |
| 96 | } | |
| 97 | } | |
| 98 | 0 | return str; |
| 99 | }; | |
| 100 | } | |
| 101 | ||
| 102 | ||
| 103 | /** | |
| 104 | * Gather some caller info 3 stack levels up. | |
| 105 | * See <http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi>. | |
| 106 | */ | |
| 107 | 1 | function getCaller3Info() { |
| 108 | 0 | var obj = {}; |
| 109 | 0 | var saveLimit = Error.stackTraceLimit; |
| 110 | 0 | var savePrepare = Error.prepareStackTrace; |
| 111 | 0 | Error.stackTraceLimit = 3; |
| 112 | 0 | Error.captureStackTrace(this, getCaller3Info); |
| 113 | 0 | Error.prepareStackTrace = function (_, stack) { |
| 114 | 0 | var caller = stack[2]; |
| 115 | 0 | obj.file = caller.getFileName(); |
| 116 | 0 | obj.line = caller.getLineNumber(); |
| 117 | 0 | var func = caller.getFunctionName(); |
| 118 | 0 | if (func) |
| 119 | 0 | obj.func = func; |
| 120 | }; | |
| 121 | 0 | this.stack; |
| 122 | 0 | Error.stackTraceLimit = saveLimit; |
| 123 | 0 | Error.prepareStackTrace = savePrepare; |
| 124 | 0 | return obj; |
| 125 | } | |
| 126 | ||
| 127 | ||
| 128 | /** | |
| 129 | * Warn about an bunyan processing error. | |
| 130 | * | |
| 131 | * If file/line are given, this makes an attempt to warn on stderr only once. | |
| 132 | * | |
| 133 | * @param msg {String} Message with which to warn. | |
| 134 | * @param file {String} Optional. File path relevant for the warning. | |
| 135 | * @param line {String} Optional. Line number in `file` path relevant for | |
| 136 | * the warning. | |
| 137 | */ | |
| 138 | 1 | function _warn(msg, file, line) { |
| 139 | 0 | assert.ok(msg); |
| 140 | 0 | var key; |
| 141 | 0 | if (file && line) { |
| 142 | 0 | key = file + ':' + line; |
| 143 | 0 | if (_warned[key]) { |
| 144 | 0 | return; |
| 145 | } | |
| 146 | 0 | _warned[key] = true; |
| 147 | } | |
| 148 | 0 | process.stderr.write(msg + '\n'); |
| 149 | } | |
| 150 | 1 | var _warned = {}; |
| 151 | ||
| 152 | ||
| 153 | ||
| 154 | //---- Levels | |
| 155 | ||
| 156 | 1 | var TRACE = 10; |
| 157 | 1 | var DEBUG = 20; |
| 158 | 1 | var INFO = 30; |
| 159 | 1 | var WARN = 40; |
| 160 | 1 | var ERROR = 50; |
| 161 | 1 | var FATAL = 60; |
| 162 | ||
| 163 | 1 | var levelFromName = { |
| 164 | 'trace': TRACE, | |
| 165 | 'debug': DEBUG, | |
| 166 | 'info': INFO, | |
| 167 | 'warn': WARN, | |
| 168 | 'error': ERROR, | |
| 169 | 'fatal': FATAL | |
| 170 | }; | |
| 171 | ||
| 172 | // Dtrace probes. | |
| 173 | 1 | var dtp = undefined; |
| 174 | 1 | var probes = dtrace && {}; |
| 175 | ||
| 176 | /** | |
| 177 | * Resolve a level number, name (upper or lowercase) to a level number value. | |
| 178 | * | |
| 179 | * @api public | |
| 180 | */ | |
| 181 | 1 | function resolveLevel(nameOrNum) { |
| 182 | 1 | var level = (typeof (nameOrNum) === 'string' |
| 183 | ? levelFromName[nameOrNum.toLowerCase()] | |
| 184 | : nameOrNum); | |
| 185 | 1 | if (! (TRACE <= level && level <= FATAL)) { |
| 186 | 0 | throw new Error('invalid level: ' + nameOrNum); |
| 187 | } | |
| 188 | 1 | return level; |
| 189 | } | |
| 190 | ||
| 191 | ||
| 192 | ||
| 193 | //---- Logger class | |
| 194 | ||
| 195 | /** | |
| 196 | * Create a Logger instance. | |
| 197 | * | |
| 198 | * @param options {Object} See documentation for full details. At minimum | |
| 199 | * this must include a 'name' string key. Configuration keys: | |
| 200 | * - `streams`: specify the logger output streams. This is an array of | |
| 201 | * objects with these fields: | |
| 202 | * - `type`: The stream type. See README.md for full details. | |
| 203 | * Often this is implied by the other fields. Examples are | |
| 204 | * 'file', 'stream' and "raw". | |
| 205 | * - `level`: Defaults to 'info'. | |
| 206 | * - `path` or `stream`: The specify the file path or writeable | |
| 207 | * stream to which log records are written. E.g. | |
| 208 | * `stream: process.stdout`. | |
| 209 | * - `closeOnExit` (boolean): Optional. Default is true for a | |
| 210 | * 'file' stream when `path` is given, false otherwise. | |
| 211 | * See README.md for full details. | |
| 212 | * - `level`: set the level for a single output stream (cannot be used | |
| 213 | * with `streams`) | |
| 214 | * - `stream`: the output stream for a logger with just one, e.g. | |
| 215 | * `process.stdout` (cannot be used with `streams`) | |
| 216 | * - `serializers`: object mapping log record field names to | |
| 217 | * serializing functions. See README.md for details. | |
| 218 | * - `src`: Boolean (default false). Set true to enable 'src' automatic | |
| 219 | * field with log call source info. | |
| 220 | * All other keys are log record fields. | |
| 221 | * | |
| 222 | * An alternative *internal* call signature is used for creating a child: | |
| 223 | * new Logger(<parent logger>, <child options>[, <child opts are simple>]); | |
| 224 | * | |
| 225 | * @param _childSimple (Boolean) An assertion that the given `_childOptions` | |
| 226 | * (a) only add fields (no config) and (b) no serialization handling is | |
| 227 | * required for them. IOW, this is a fast path for frequent child | |
| 228 | * creation. | |
| 229 | */ | |
| 230 | 1 | function Logger(options, _childOptions, _childSimple) { |
| 231 | 1 | xxx('Logger start:', options) |
| 232 | 1 | if (! this instanceof Logger) { |
| 233 | 0 | return new Logger(options, _childOptions); |
| 234 | } | |
| 235 | ||
| 236 | // Input arg validation. | |
| 237 | 1 | var parent; |
| 238 | 1 | if (_childOptions !== undefined) { |
| 239 | 0 | parent = options; |
| 240 | 0 | options = _childOptions; |
| 241 | 0 | if (! parent instanceof Logger) { |
| 242 | 0 | throw new TypeError( |
| 243 | 'invalid Logger creation: do not pass a second arg'); | |
| 244 | } | |
| 245 | } | |
| 246 | 1 | if (!options) { |
| 247 | 0 | throw new TypeError('options (object) is required'); |
| 248 | } | |
| 249 | 1 | if (!parent) { |
| 250 | 1 | if (!options.name) { |
| 251 | 0 | throw new TypeError('options.name (string) is required'); |
| 252 | } | |
| 253 | } else { | |
| 254 | 0 | if (options.name) { |
| 255 | 0 | throw new TypeError( |
| 256 | 'invalid options.name: child cannot set logger name'); | |
| 257 | } | |
| 258 | } | |
| 259 | 1 | if (options.stream && options.streams) { |
| 260 | 0 | throw new TypeError('cannot mix "streams" and "stream" options'); |
| 261 | } | |
| 262 | 1 | if (options.streams && !Array.isArray(options.streams)) { |
| 263 | 0 | throw new TypeError('invalid options.streams: must be an array') |
| 264 | } | |
| 265 | 1 | if (options.serializers && (typeof (options.serializers) !== 'object' || |
| 266 | Array.isArray(options.serializers))) { | |
| 267 | 0 | throw new TypeError('invalid options.serializers: must be an object') |
| 268 | } | |
| 269 | ||
| 270 | 1 | EventEmitter.call(this); |
| 271 | ||
| 272 | // Fast path for simple child creation. | |
| 273 | 1 | if (parent && _childSimple) { |
| 274 | // `_isSimpleChild` is a signal to stream close handling that this child | |
| 275 | // owns none of its streams. | |
| 276 | 0 | this._isSimpleChild = true; |
| 277 | ||
| 278 | 0 | this._level = parent._level; |
| 279 | 0 | this.streams = parent.streams; |
| 280 | 0 | this.serializers = parent.serializers; |
| 281 | 0 | this.src = parent.src; |
| 282 | 0 | var fields = this.fields = {}; |
| 283 | 0 | var parentFieldNames = Object.keys(parent.fields); |
| 284 | 0 | for (var i = 0; i < parentFieldNames.length; i++) { |
| 285 | 0 | var name = parentFieldNames[i]; |
| 286 | 0 | fields[name] = parent.fields[name]; |
| 287 | } | |
| 288 | 0 | var names = Object.keys(options); |
| 289 | 0 | for (var i = 0; i < names.length; i++) { |
| 290 | 0 | var name = names[i]; |
| 291 | 0 | fields[name] = options[name]; |
| 292 | } | |
| 293 | 0 | return; |
| 294 | } | |
| 295 | ||
| 296 | // Null values. | |
| 297 | 1 | var self = this; |
| 298 | 1 | if (parent) { |
| 299 | 0 | this._level = parent._level; |
| 300 | 0 | this.streams = []; |
| 301 | 0 | for (var i = 0; i < parent.streams.length; i++) { |
| 302 | 0 | var s = objCopy(parent.streams[i]); |
| 303 | 0 | s.closeOnExit = false; // Don't own parent stream. |
| 304 | 0 | this.streams.push(s); |
| 305 | } | |
| 306 | 0 | this.serializers = objCopy(parent.serializers); |
| 307 | 0 | this.src = parent.src; |
| 308 | 0 | this.fields = objCopy(parent.fields); |
| 309 | 0 | if (options.level) { |
| 310 | 0 | this.level(options.level); |
| 311 | } | |
| 312 | } else { | |
| 313 | 1 | this._level = Number.POSITIVE_INFINITY; |
| 314 | 1 | this.streams = []; |
| 315 | 1 | this.serializers = null; |
| 316 | 1 | this.src = false; |
| 317 | 1 | this.fields = {}; |
| 318 | } | |
| 319 | ||
| 320 | 1 | if (!dtp && dtrace) { |
| 321 | 1 | dtp = dtrace.createDTraceProvider('bunyan'); |
| 322 | ||
| 323 | 1 | for (var level in levelFromName) { |
| 324 | 6 | var probe; |
| 325 | ||
| 326 | 6 | probes[levelFromName[level]] = probe = |
| 327 | dtp.addProbe('log-' + level, 'char *'); | |
| 328 | ||
| 329 | // Explicitly add a reference to dtp to prevent it from being GC'd | |
| 330 | 6 | probe.dtp = dtp; |
| 331 | } | |
| 332 | ||
| 333 | 1 | dtp.enable(); |
| 334 | } | |
| 335 | ||
| 336 | // Helpers | |
| 337 | 1 | function addStream(s) { |
| 338 | 1 | s = objCopy(s); |
| 339 | ||
| 340 | // Implicit 'type' from other args. | |
| 341 | 1 | var type = s.type; |
| 342 | 1 | if (!s.type) { |
| 343 | 0 | if (s.stream) { |
| 344 | 0 | s.type = 'stream'; |
| 345 | 0 | } else if (s.path) { |
| 346 | 0 | s.type = 'file' |
| 347 | } | |
| 348 | } | |
| 349 | 1 | s.raw = (s.type === 'raw'); // PERF: Allow for faster check in `_emit`. |
| 350 | ||
| 351 | 1 | if (s.level) { |
| 352 | 1 | s.level = resolveLevel(s.level); |
| 353 | 0 | } else if (options.level) { |
| 354 | 0 | s.level = resolveLevel(options.level); |
| 355 | } else { | |
| 356 | 0 | s.level = INFO; |
| 357 | } | |
| 358 | 1 | if (s.level < self._level) { |
| 359 | 1 | self._level = s.level; |
| 360 | } | |
| 361 | ||
| 362 | 1 | switch (s.type) { |
| 363 | case 'stream': | |
| 364 | 1 | if (!s.closeOnExit) { |
| 365 | 1 | s.closeOnExit = false; |
| 366 | } | |
| 367 | 1 | break; |
| 368 | case 'file': | |
| 369 | 0 | if (!s.stream) { |
| 370 | 0 | s.stream = fs.createWriteStream(s.path, |
| 371 | {flags: 'a', encoding: 'utf8'}); | |
| 372 | 0 | s.stream.on('error', function (err) { |
| 373 | 0 | self.emit('error', err, s); |
| 374 | }); | |
| 375 | 0 | if (!s.closeOnExit) { |
| 376 | 0 | s.closeOnExit = true; |
| 377 | } | |
| 378 | } else { | |
| 379 | 0 | if (!s.closeOnExit) { |
| 380 | 0 | s.closeOnExit = false; |
| 381 | } | |
| 382 | } | |
| 383 | 0 | break; |
| 384 | case 'rotating-file': | |
| 385 | 0 | assert.ok(!s.stream, |
| 386 | '"rotating-file" stream should not give a "stream"'); | |
| 387 | 0 | assert.ok(s.path); |
| 388 | 0 | assert.ok(mv, '"rotating-file" stream type is not supported: ' |
| 389 | + 'missing "mv" module'); | |
| 390 | 0 | s.stream = new RotatingFileStream(s); |
| 391 | 0 | if (!s.closeOnExit) { |
| 392 | 0 | s.closeOnExit = true; |
| 393 | } | |
| 394 | 0 | break; |
| 395 | case 'raw': | |
| 396 | 0 | if (!s.closeOnExit) { |
| 397 | 0 | s.closeOnExit = false; |
| 398 | } | |
| 399 | 0 | break; |
| 400 | default: | |
| 401 | 0 | throw new TypeError('unknown stream type "' + s.type + '"'); |
| 402 | } | |
| 403 | ||
| 404 | 1 | self.streams.push(s); |
| 405 | } | |
| 406 | ||
| 407 | 1 | function addSerializers(serializers) { |
| 408 | 0 | if (!self.serializers) { |
| 409 | 0 | self.serializers = {}; |
| 410 | } | |
| 411 | 0 | Object.keys(serializers).forEach(function (field) { |
| 412 | 0 | var serializer = serializers[field]; |
| 413 | 0 | if (typeof (serializer) !== 'function') { |
| 414 | 0 | throw new TypeError(format( |
| 415 | 'invalid serializer for "%s" field: must be a function', | |
| 416 | field)); | |
| 417 | } else { | |
| 418 | 0 | self.serializers[field] = serializer; |
| 419 | } | |
| 420 | }); | |
| 421 | } | |
| 422 | ||
| 423 | // Handle *config* options (i.e. options that are not just plain data | |
| 424 | // for log records). | |
| 425 | 1 | if (options.stream) { |
| 426 | 0 | addStream({ |
| 427 | type: 'stream', | |
| 428 | stream: options.stream, | |
| 429 | closeOnExit: false, | |
| 430 | level: (options.level ? resolveLevel(options.level) : INFO) | |
| 431 | }); | |
| 432 | 1 | } else if (options.streams) { |
| 433 | 0 | options.streams.forEach(addStream); |
| 434 | 1 | } else if (parent && options.level) { |
| 435 | 0 | this.level(options.level); |
| 436 | 1 | } else if (!parent) { |
| 437 | 1 | addStream({ |
| 438 | type: 'stream', | |
| 439 | stream: process.stdout, | |
| 440 | closeOnExit: false, | |
| 441 | level: (options.level ? resolveLevel(options.level) : INFO) | |
| 442 | }); | |
| 443 | } | |
| 444 | 1 | if (options.serializers) { |
| 445 | 0 | addSerializers(options.serializers); |
| 446 | } | |
| 447 | 1 | if (options.src) { |
| 448 | 0 | this.src = true; |
| 449 | } | |
| 450 | 1 | xxx('Logger: ', self) |
| 451 | ||
| 452 | // Fields. | |
| 453 | // These are the default fields for log records (minus the attributes | |
| 454 | // removed in this constructor). To allow storing raw log records | |
| 455 | // (unrendered), `this.fields` must never be mutated. Create a copy for | |
| 456 | // any changes. | |
| 457 | 1 | var fields = objCopy(options); |
| 458 | 1 | delete fields.stream; |
| 459 | 1 | delete fields.level; |
| 460 | 1 | delete fields.streams; |
| 461 | 1 | delete fields.serializers; |
| 462 | 1 | delete fields.src; |
| 463 | 1 | if (this.serializers) { |
| 464 | 0 | this._applySerializers(fields); |
| 465 | } | |
| 466 | 1 | if (!fields.hostname) { |
| 467 | 1 | fields.hostname = os.hostname(); |
| 468 | } | |
| 469 | 1 | if (!fields.pid) { |
| 470 | 1 | fields.pid = process.pid; |
| 471 | } | |
| 472 | 1 | Object.keys(fields).forEach(function (k) { |
| 473 | 3 | self.fields[k] = fields[k]; |
| 474 | }); | |
| 475 | } | |
| 476 | ||
| 477 | 1 | util.inherits(Logger, EventEmitter); |
| 478 | ||
| 479 | ||
| 480 | /** | |
| 481 | * Create a child logger, typically to add a few log record fields. | |
| 482 | * | |
| 483 | * This can be useful when passing a logger to a sub-component, e.g. a | |
| 484 | * 'wuzzle' component of your service: | |
| 485 | * | |
| 486 | * var wuzzleLog = log.child({component: 'wuzzle'}) | |
| 487 | * var wuzzle = new Wuzzle({..., log: wuzzleLog}) | |
| 488 | * | |
| 489 | * Then log records from the wuzzle code will have the same structure as | |
| 490 | * the app log, *plus the component='wuzzle' field*. | |
| 491 | * | |
| 492 | * @param options {Object} Optional. Set of options to apply to the child. | |
| 493 | * All of the same options for a new Logger apply here. Notes: | |
| 494 | * - The parent's streams are inherited and cannot be removed in this | |
| 495 | * call. Any given `streams` are *added* to the set inherited from | |
| 496 | * the parent. | |
| 497 | * - The parent's serializers are inherited, though can effectively be | |
| 498 | * overwritten by using duplicate keys. | |
| 499 | * - Can use `level` to set the level of the streams inherited from | |
| 500 | * the parent. The level for the parent is NOT affected. | |
| 501 | * @param simple {Boolean} Optional. Set to true to assert that `options` | |
| 502 | * (a) only add fields (no config) and (b) no serialization handling is | |
| 503 | * required for them. IOW, this is a fast path for frequent child | |
| 504 | * creation. See 'tools/timechild.js' for numbers. | |
| 505 | */ | |
| 506 | 1 | Logger.prototype.child = function (options, simple) { |
| 507 | 0 | return new Logger(this, options || {}, simple); |
| 508 | } | |
| 509 | ||
| 510 | ||
| 511 | /** | |
| 512 | * A convenience method to reopen 'file' streams on a logger. This can be | |
| 513 | * useful with external log rotation utilities that move and re-open log files | |
| 514 | * (e.g. logrotate on Linux, logadm on SmartOS/Illumos). Those utilities | |
| 515 | * typically have rotation options to copy-and-truncate the log file, but | |
| 516 | * you may not want to use that. An alternative is to do this in your | |
| 517 | * application: | |
| 518 | * | |
| 519 | * var log = bunyan.createLogger(...); | |
| 520 | * ... | |
| 521 | * process.on('SIGUSR2', function () { | |
| 522 | * log.reopenFileStreams(); | |
| 523 | * }); | |
| 524 | * ... | |
| 525 | * | |
| 526 | * See <https://github.com/trentm/node-bunyan/issues/104>. | |
| 527 | */ | |
| 528 | 1 | Logger.prototype.reopenFileStreams = function () { |
| 529 | 0 | var self = this; |
| 530 | 0 | self.streams.forEach(function (s) { |
| 531 | 0 | if (s.type === 'file') { |
| 532 | 0 | if (s.stream) { |
| 533 | // Not sure if typically would want this, or more immediate | |
| 534 | // `s.stream.destroy()`. | |
| 535 | 0 | s.stream.end(); |
| 536 | 0 | s.stream.destroySoon(); |
| 537 | 0 | delete s.stream; |
| 538 | } | |
| 539 | 0 | s.stream = fs.createWriteStream(s.path, |
| 540 | {flags: 'a', encoding: 'utf8'}); | |
| 541 | 0 | s.stream.on('error', function (err) { |
| 542 | 0 | self.emit('error', err, s); |
| 543 | }); | |
| 544 | } | |
| 545 | }); | |
| 546 | }; | |
| 547 | ||
| 548 | ||
| 549 | /* BEGIN JSSTYLED */ | |
| 550 | /** | |
| 551 | * Close this logger. | |
| 552 | * | |
| 553 | * This closes streams (that it owns, as per 'endOnClose' attributes on | |
| 554 | * streams), etc. Typically you **don't** need to bother calling this. | |
| 555 | Logger.prototype.close = function () { | |
| 556 | if (this._closed) { | |
| 557 | return; | |
| 558 | } | |
| 559 | if (!this._isSimpleChild) { | |
| 560 | self.streams.forEach(function (s) { | |
| 561 | if (s.endOnClose) { | |
| 562 | xxx('closing stream s:', s); | |
| 563 | s.stream.end(); | |
| 564 | s.endOnClose = false; | |
| 565 | } | |
| 566 | }); | |
| 567 | } | |
| 568 | this._closed = true; | |
| 569 | } | |
| 570 | */ | |
| 571 | /* END JSSTYLED */ | |
| 572 | ||
| 573 | ||
| 574 | /** | |
| 575 | * Get/set the level of all streams on this logger. | |
| 576 | * | |
| 577 | * Get Usage: | |
| 578 | * // Returns the current log level (lowest level of all its streams). | |
| 579 | * log.level() -> INFO | |
| 580 | * | |
| 581 | * Set Usage: | |
| 582 | * log.level(INFO) // set all streams to level INFO | |
| 583 | * log.level('info') // can use 'info' et al aliases | |
| 584 | */ | |
| 585 | 1 | Logger.prototype.level = function level(value) { |
| 586 | 0 | if (value === undefined) { |
| 587 | 0 | return this._level; |
| 588 | } | |
| 589 | 0 | var newLevel = resolveLevel(value); |
| 590 | 0 | var len = this.streams.length; |
| 591 | 0 | for (var i = 0; i < len; i++) { |
| 592 | 0 | this.streams[i].level = newLevel; |
| 593 | } | |
| 594 | 0 | this._level = newLevel; |
| 595 | } | |
| 596 | ||
| 597 | ||
| 598 | /** | |
| 599 | * Get/set the level of a particular stream on this logger. | |
| 600 | * | |
| 601 | * Get Usage: | |
| 602 | * // Returns an array of the levels of each stream. | |
| 603 | * log.levels() -> [TRACE, INFO] | |
| 604 | * | |
| 605 | * // Returns a level of the identified stream. | |
| 606 | * log.levels(0) -> TRACE // level of stream at index 0 | |
| 607 | * log.levels('foo') // level of stream with name 'foo' | |
| 608 | * | |
| 609 | * Set Usage: | |
| 610 | * log.levels(0, INFO) // set level of stream 0 to INFO | |
| 611 | * log.levels(0, 'info') // can use 'info' et al aliases | |
| 612 | * log.levels('foo', WARN) // set stream named 'foo' to WARN | |
| 613 | * | |
| 614 | * Stream names: When streams are defined, they can optionally be given | |
| 615 | * a name. For example, | |
| 616 | * log = new Logger({ | |
| 617 | * streams: [ | |
| 618 | * { | |
| 619 | * name: 'foo', | |
| 620 | * path: '/var/log/my-service/foo.log' | |
| 621 | * level: 'trace' | |
| 622 | * }, | |
| 623 | * ... | |
| 624 | * | |
| 625 | * @param name {String|Number} The stream index or name. | |
| 626 | * @param value {Number|String} The level value (INFO) or alias ('info'). | |
| 627 | * If not given, this is a 'get' operation. | |
| 628 | * @throws {Error} If there is no stream with the given name. | |
| 629 | */ | |
| 630 | 1 | Logger.prototype.levels = function levels(name, value) { |
| 631 | 0 | if (name === undefined) { |
| 632 | 0 | assert.equal(value, undefined); |
| 633 | 0 | return this.streams.map( |
| 634 | 0 | function (s) { return s.level }); |
| 635 | } | |
| 636 | 0 | var stream; |
| 637 | 0 | if (typeof (name) === 'number') { |
| 638 | 0 | stream = this.streams[name]; |
| 639 | 0 | if (stream === undefined) { |
| 640 | 0 | throw new Error('invalid stream index: ' + name); |
| 641 | } | |
| 642 | } else { | |
| 643 | 0 | var len = this.streams.length; |
| 644 | 0 | for (var i = 0; i < len; i++) { |
| 645 | 0 | var s = this.streams[i]; |
| 646 | 0 | if (s.name === name) { |
| 647 | 0 | stream = s; |
| 648 | 0 | break; |
| 649 | } | |
| 650 | } | |
| 651 | 0 | if (!stream) { |
| 652 | 0 | throw new Error(format('no stream with name "%s"', name)); |
| 653 | } | |
| 654 | } | |
| 655 | 0 | if (value === undefined) { |
| 656 | 0 | return stream.level; |
| 657 | } else { | |
| 658 | 0 | var newLevel = resolveLevel(value); |
| 659 | 0 | stream.level = newLevel; |
| 660 | 0 | if (newLevel < this._level) { |
| 661 | 0 | this._level = newLevel; |
| 662 | } | |
| 663 | } | |
| 664 | } | |
| 665 | ||
| 666 | ||
| 667 | /** | |
| 668 | * Apply registered serializers to the appropriate keys in the given fields. | |
| 669 | * | |
| 670 | * Pre-condition: This is only called if there is at least one serializer. | |
| 671 | * | |
| 672 | * @param fields (Object) The log record fields. | |
| 673 | * @param excludeFields (Object) Optional mapping of keys to `true` for | |
| 674 | * keys to NOT apply a serializer. | |
| 675 | */ | |
| 676 | 1 | Logger.prototype._applySerializers = function (fields, excludeFields) { |
| 677 | 0 | var self = this; |
| 678 | ||
| 679 | 0 | xxx('_applySerializers: excludeFields', excludeFields); |
| 680 | ||
| 681 | // Check each serializer against these (presuming number of serializers | |
| 682 | // is typically less than number of fields). | |
| 683 | 0 | Object.keys(this.serializers).forEach(function (name) { |
| 684 | 0 | if (fields[name] === undefined || |
| 685 | (excludeFields && excludeFields[name])) | |
| 686 | { | |
| 687 | 0 | return; |
| 688 | } | |
| 689 | 0 | xxx('_applySerializers; apply to "%s" key', name) |
| 690 | 0 | try { |
| 691 | 0 | fields[name] = self.serializers[name](fields[name]); |
| 692 | } catch (err) { | |
| 693 | 0 | _warn(format('bunyan: ERROR: This should never happen. ' |
| 694 | + 'This is a bug in <https://github.com/trentm/node-bunyan> ' | |
| 695 | + 'or in this application. Exception from "%s" Logger ' | |
| 696 | + 'serializer: %s', | |
| 697 | name, err.stack || err)); | |
| 698 | 0 | fields[name] = format('(Error in Bunyan log "%s" serializer ' |
| 699 | + 'broke field. See stderr for details.)', name); | |
| 700 | } | |
| 701 | }); | |
| 702 | } | |
| 703 | ||
| 704 | ||
| 705 | /** | |
| 706 | * Emit a log record. | |
| 707 | * | |
| 708 | * @param rec {log record} | |
| 709 | * @param noemit {Boolean} Optional. Set to true to skip emission | |
| 710 | * and just return the JSON string. | |
| 711 | */ | |
| 712 | 1 | Logger.prototype._emit = function (rec, noemit) { |
| 713 | 1 | var i; |
| 714 | ||
| 715 | // Lazily determine if this Logger has non-'raw' streams. If there are | |
| 716 | // any, then we need to stringify the log record. | |
| 717 | 1 | if (this.haveNonRawStreams === undefined) { |
| 718 | 1 | this.haveNonRawStreams = false; |
| 719 | 1 | for (i = 0; i < this.streams.length; i++) { |
| 720 | 1 | if (!this.streams[i].raw) { |
| 721 | 1 | this.haveNonRawStreams = true; |
| 722 | 1 | break; |
| 723 | } | |
| 724 | } | |
| 725 | } | |
| 726 | ||
| 727 | // Stringify the object. Attempt to warn/recover on error. | |
| 728 | 1 | var str; |
| 729 | 1 | if (noemit || this.haveNonRawStreams) { |
| 730 | 1 | str = JSON.stringify(rec, safeCycles()) + '\n'; |
| 731 | } | |
| 732 | ||
| 733 | 1 | if (noemit) |
| 734 | 0 | return str; |
| 735 | ||
| 736 | 1 | var level = rec.level; |
| 737 | 1 | for (i = 0; i < this.streams.length; i++) { |
| 738 | 1 | var s = this.streams[i]; |
| 739 | 1 | if (s.level <= level) { |
| 740 | 1 | xxx('writing log rec "%s" to "%s" stream (%d <= %d): %j', |
| 741 | rec.msg, s.type, s.level, level, rec); | |
| 742 | 1 | s.stream.write(s.raw ? rec : str); |
| 743 | } | |
| 744 | }; | |
| 745 | ||
| 746 | 1 | return str; |
| 747 | } | |
| 748 | ||
| 749 | ||
| 750 | /** | |
| 751 | * Build a log emitter function for level minLevel. I.e. this is the | |
| 752 | * creator of `log.info`, `log.error`, etc. | |
| 753 | */ | |
| 754 | 1 | function mkLogEmitter(minLevel) { |
| 755 | 6 | return function () { |
| 756 | 1 | var log = this; |
| 757 | ||
| 758 | 1 | function mkRecord(args) { |
| 759 | 1 | var excludeFields; |
| 760 | 1 | if (args[0] instanceof Error) { |
| 761 | // `log.<level>(err, ...)` | |
| 762 | 0 | fields = {err: errSerializer(args[0])}; |
| 763 | 0 | excludeFields = {err: true}; |
| 764 | 0 | if (args.length === 1) { |
| 765 | 0 | msgArgs = [fields.err.message]; |
| 766 | } else { | |
| 767 | 0 | msgArgs = Array.prototype.slice.call(args, 1); |
| 768 | } | |
| 769 | 1 | } else if (typeof (args[0]) === 'string') { |
| 770 | // `log.<level>(msg, ...)` | |
| 771 | 1 | fields = null; |
| 772 | 1 | msgArgs = Array.prototype.slice.call(args); |
| 773 | 0 | } else if (Buffer.isBuffer(args[0])) { // `log.<level>(buf, ...)` |
| 774 | // Almost certainly an error, show `inspect(buf)`. See bunyan | |
| 775 | // issue #35. | |
| 776 | 0 | fields = null; |
| 777 | 0 | msgArgs = Array.prototype.slice.call(args); |
| 778 | 0 | msgArgs[0] = util.inspect(msgArgs[0]); |
| 779 | } else { // `log.<level>(fields, msg, ...)` | |
| 780 | 0 | fields = args[0]; |
| 781 | 0 | msgArgs = Array.prototype.slice.call(args, 1); |
| 782 | } | |
| 783 | ||
| 784 | // Build up the record object. | |
| 785 | 1 | var rec = objCopy(log.fields); |
| 786 | 1 | var level = rec.level = minLevel; |
| 787 | 1 | var recFields = (fields ? objCopy(fields) : null); |
| 788 | 1 | if (recFields) { |
| 789 | 0 | if (log.serializers) { |
| 790 | 0 | log._applySerializers(recFields, excludeFields); |
| 791 | } | |
| 792 | 0 | Object.keys(recFields).forEach(function (k) { |
| 793 | 0 | rec[k] = recFields[k]; |
| 794 | }); | |
| 795 | } | |
| 796 | 1 | rec.msg = format.apply(log, msgArgs); |
| 797 | 1 | if (!rec.time) { |
| 798 | 1 | rec.time = (new Date()); |
| 799 | } | |
| 800 | // Get call source info | |
| 801 | 1 | if (log.src && !rec.src) { |
| 802 | 0 | rec.src = getCaller3Info() |
| 803 | } | |
| 804 | 1 | rec.v = LOG_VERSION; |
| 805 | ||
| 806 | 1 | return rec; |
| 807 | }; | |
| 808 | ||
| 809 | 1 | var fields = null; |
| 810 | 1 | var msgArgs = arguments; |
| 811 | 1 | var str = null; |
| 812 | 1 | var rec = null; |
| 813 | 1 | if (arguments.length === 0) { // `log.<level>()` |
| 814 | 0 | return (this._level <= minLevel); |
| 815 | 1 | } else if (this._level > minLevel) { |
| 816 | /* pass through */ | |
| 817 | } else { | |
| 818 | 1 | rec = mkRecord(msgArgs); |
| 819 | 1 | str = this._emit(rec); |
| 820 | } | |
| 821 | 1 | probes && probes[minLevel].fire(function () { |
| 822 | 0 | return [ str || |
| 823 | (rec && log._emit(rec, true)) || | |
| 824 | log._emit(mkRecord(msgArgs), true) ]; | |
| 825 | }); | |
| 826 | } | |
| 827 | } | |
| 828 | ||
| 829 | ||
| 830 | /** | |
| 831 | * The functions below log a record at a specific level. | |
| 832 | * | |
| 833 | * Usages: | |
| 834 | * log.<level>() -> boolean is-trace-enabled | |
| 835 | * log.<level>(<Error> err, [<string> msg, ...]) | |
| 836 | * log.<level>(<string> msg, ...) | |
| 837 | * log.<level>(<object> fields, <string> msg, ...) | |
| 838 | * | |
| 839 | * where <level> is the lowercase version of the log level. E.g.: | |
| 840 | * | |
| 841 | * log.info() | |
| 842 | * | |
| 843 | * @params fields {Object} Optional set of additional fields to log. | |
| 844 | * @params msg {String} Log message. This can be followed by additional | |
| 845 | * arguments that are handled like | |
| 846 | * [util.format](http://nodejs.org/docs/latest/api/all.html#util.format). | |
| 847 | */ | |
| 848 | 1 | Logger.prototype.trace = mkLogEmitter(TRACE); |
| 849 | 1 | Logger.prototype.debug = mkLogEmitter(DEBUG); |
| 850 | 1 | Logger.prototype.info = mkLogEmitter(INFO); |
| 851 | 1 | Logger.prototype.warn = mkLogEmitter(WARN); |
| 852 | 1 | Logger.prototype.error = mkLogEmitter(ERROR); |
| 853 | 1 | Logger.prototype.fatal = mkLogEmitter(FATAL); |
| 854 | ||
| 855 | ||
| 856 | ||
| 857 | //---- Standard serializers | |
| 858 | // A serializer is a function that serializes a JavaScript object to a | |
| 859 | // JSON representation for logging. There is a standard set of presumed | |
| 860 | // interesting objects in node.js-land. | |
| 861 | ||
| 862 | 1 | Logger.stdSerializers = {}; |
| 863 | ||
| 864 | // Serialize an HTTP request. | |
| 865 | 1 | Logger.stdSerializers.req = function req(req) { |
| 866 | 0 | if (!req || !req.connection) |
| 867 | 0 | return req; |
| 868 | 0 | return { |
| 869 | method: req.method, | |
| 870 | url: req.url, | |
| 871 | headers: req.headers, | |
| 872 | remoteAddress: req.connection.remoteAddress, | |
| 873 | remotePort: req.connection.remotePort | |
| 874 | }; | |
| 875 | // Trailers: Skipping for speed. If you need trailers in your app, then | |
| 876 | // make a custom serializer. | |
| 877 | //if (Object.keys(trailers).length > 0) { | |
| 878 | // obj.trailers = req.trailers; | |
| 879 | //} | |
| 880 | }; | |
| 881 | ||
| 882 | // Serialize an HTTP response. | |
| 883 | 1 | Logger.stdSerializers.res = function res(res) { |
| 884 | 0 | if (!res || !res.statusCode) |
| 885 | 0 | return res; |
| 886 | 0 | return { |
| 887 | statusCode: res.statusCode, | |
| 888 | header: res._header | |
| 889 | } | |
| 890 | }; | |
| 891 | ||
| 892 | ||
| 893 | /* | |
| 894 | * This function dumps long stack traces for exceptions having a cause() | |
| 895 | * method. The error classes from | |
| 896 | * [verror](https://github.com/davepacheco/node-verror) and | |
| 897 | * [restify v2.0](https://github.com/mcavage/node-restify) are examples. | |
| 898 | * | |
| 899 | * Based on `dumpException` in | |
| 900 | * https://github.com/davepacheco/node-extsprintf/blob/master/lib/extsprintf.js | |
| 901 | */ | |
| 902 | 1 | function getFullErrorStack(ex) |
| 903 | { | |
| 904 | 0 | var ret = ex.stack || ex.toString(); |
| 905 | 0 | if (ex.cause && typeof (ex.cause) === 'function') { |
| 906 | 0 | var cex = ex.cause(); |
| 907 | 0 | if (cex) { |
| 908 | 0 | ret += '\nCaused by: ' + getFullErrorStack(cex); |
| 909 | } | |
| 910 | } | |
| 911 | 0 | return (ret); |
| 912 | } | |
| 913 | ||
| 914 | // Serialize an Error object | |
| 915 | // (Core error properties are enumerable in node 0.4, not in 0.6). | |
| 916 | 1 | var errSerializer = Logger.stdSerializers.err = function err(err) { |
| 917 | 0 | if (!err || !err.stack) |
| 918 | 0 | return err; |
| 919 | 0 | var obj = { |
| 920 | message: err.message, | |
| 921 | name: err.name, | |
| 922 | stack: getFullErrorStack(err), | |
| 923 | code: err.code, | |
| 924 | signal: err.signal | |
| 925 | } | |
| 926 | 0 | return obj; |
| 927 | }; | |
| 928 | ||
| 929 | ||
| 930 | // A JSON stringifier that handles cycles safely. | |
| 931 | // Usage: JSON.stringify(obj, safeCycles()) | |
| 932 | 1 | function safeCycles() { |
| 933 | 1 | var seen = []; |
| 934 | 1 | return function (key, val) { |
| 935 | 8 | if (!val || typeof (val) !== 'object') { |
| 936 | 7 | return val; |
| 937 | } | |
| 938 | 1 | if (seen.indexOf(val) !== -1) { |
| 939 | 0 | return '[Circular]'; |
| 940 | } | |
| 941 | 1 | seen.push(val); |
| 942 | 1 | return val; |
| 943 | }; | |
| 944 | } | |
| 945 | ||
| 946 | ||
| 947 | /** | |
| 948 | * XXX | |
| 949 | */ | |
| 950 | 1 | if (mv) { |
| 951 | ||
| 952 | 1 | function RotatingFileStream(options) { |
| 953 | 0 | this.path = options.path; |
| 954 | 0 | this.stream = fs.createWriteStream(this.path, |
| 955 | {flags: 'a', encoding: 'utf8'}); | |
| 956 | 0 | this.count = (options.count == null ? 10 : options.count); |
| 957 | 0 | assert.ok(typeof (this.count) === 'number' && this.count >= 0); |
| 958 | ||
| 959 | // Parse `options.period`. | |
| 960 | 0 | if (options.period) { |
| 961 | // <number><scope> where scope is: | |
| 962 | // h hours (at the start of the hour) | |
| 963 | // d days (at the start of the day, i.e. just after midnight) | |
| 964 | // w weeks (at the start of Sunday) | |
| 965 | // m months (on the first of the month) | |
| 966 | // y years (at the start of Jan 1st) | |
| 967 | // with special values 'hourly' (1h), 'daily' (1d), "weekly" (1w), | |
| 968 | // 'monthly' (1m) and 'yearly' (1y) | |
| 969 | 0 | var period = { |
| 970 | 'hourly': '1h', | |
| 971 | 'daily': '1d', | |
| 972 | 'weekly': '1w', | |
| 973 | 'monthly': '1m', | |
| 974 | 'yearly': '1y' | |
| 975 | }[options.period] || options.period; | |
| 976 | 0 | var m = /^([1-9][0-9]*)([hdwmy]|ms)$/.exec(period); |
| 977 | 0 | if (!m) { |
| 978 | 0 | throw new Error(format('invalid period: "%s"', options.period)); |
| 979 | } | |
| 980 | 0 | this.periodNum = Number(m[1]); |
| 981 | 0 | this.periodScope = m[2]; |
| 982 | } else { | |
| 983 | 0 | this.periodNum = 1; |
| 984 | 0 | this.periodScope = 'd'; |
| 985 | } | |
| 986 | ||
| 987 | // TODO: template support for backup files | |
| 988 | // template: <path to which to rotate> | |
| 989 | // default is %P.%n | |
| 990 | // '/var/log/archive/foo.log' -> foo.log.%n | |
| 991 | // '/var/log/archive/foo.log.%n' | |
| 992 | // codes: | |
| 993 | // XXX support strftime codes (per node version of those) | |
| 994 | // or whatever module. Pick non-colliding for extra | |
| 995 | // codes | |
| 996 | // %P `path` base value | |
| 997 | // %n integer number of rotated log (1,2,3,...) | |
| 998 | // %d datetime in YYYY-MM-DD_HH-MM-SS | |
| 999 | // XXX what should default date format be? | |
| 1000 | // prior art? Want to avoid ':' in | |
| 1001 | // filenames (illegal on Windows for one). | |
| 1002 | ||
| 1003 | 0 | this.rotQueue = []; |
| 1004 | 0 | this.rotating = false; |
| 1005 | 0 | this._setupNextRot(); |
| 1006 | } | |
| 1007 | ||
| 1008 | 1 | util.inherits(RotatingFileStream, EventEmitter); |
| 1009 | ||
| 1010 | 1 | RotatingFileStream.prototype._setupNextRot = function () { |
| 1011 | 0 | var self = this; |
| 1012 | 0 | this.rotAt = this._nextRotTime(); |
| 1013 | 0 | this.timeout = setTimeout( |
| 1014 | 0 | function () { self.rotate(); }, |
| 1015 | this.rotAt - Date.now()); | |
| 1016 | } | |
| 1017 | ||
| 1018 | 1 | RotatingFileStream.prototype._nextRotTime = function _nextRotTime(first) { |
| 1019 | 0 | var _DEBUG = false; |
| 1020 | 0 | if (_DEBUG) |
| 1021 | 0 | console.log('-- _nextRotTime: %s%s', this.periodNum, this.periodScope); |
| 1022 | 0 | var d = new Date(); |
| 1023 | ||
| 1024 | 0 | if (_DEBUG) console.log(' now local: %s', d); |
| 1025 | 0 | if (_DEBUG) console.log(' now utc: %s', d.toISOString()); |
| 1026 | 0 | var rotAt; |
| 1027 | 0 | switch (this.periodScope) { |
| 1028 | case 'ms': | |
| 1029 | // Hidden millisecond period for debugging. | |
| 1030 | 0 | if (this.rotAt) { |
| 1031 | 0 | rotAt = this.rotAt + this.periodNum; |
| 1032 | } else { | |
| 1033 | 0 | rotAt = Date.now() + this.periodNum; |
| 1034 | } | |
| 1035 | 0 | break; |
| 1036 | case 'h': | |
| 1037 | 0 | if (this.rotAt) { |
| 1038 | 0 | rotAt = this.rotAt + this.periodNum * 60 * 60 * 1000; |
| 1039 | } else { | |
| 1040 | // First time: top of the next hour. | |
| 1041 | 0 | rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
| 1042 | d.getUTCDate(), d.getUTCHours() + 1); | |
| 1043 | } | |
| 1044 | 0 | break; |
| 1045 | case 'd': | |
| 1046 | 0 | if (this.rotAt) { |
| 1047 | 0 | rotAt = this.rotAt + this.periodNum * 24 * 60 * 60 * 1000; |
| 1048 | } else { | |
| 1049 | // First time: start of tomorrow (i.e. at the coming midnight) UTC. | |
| 1050 | 0 | rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
| 1051 | d.getUTCDate() + 1); | |
| 1052 | } | |
| 1053 | 0 | break; |
| 1054 | case 'w': | |
| 1055 | // Currently, always on Sunday morning at 00:00:00 (UTC). | |
| 1056 | 0 | if (this.rotAt) { |
| 1057 | 0 | rotAt = this.rotAt + this.periodNum * 7 * 24 * 60 * 60 * 1000; |
| 1058 | } else { | |
| 1059 | // First time: this coming Sunday. | |
| 1060 | 0 | rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), |
| 1061 | d.getUTCDate() + (7 - d.getUTCDay())); | |
| 1062 | } | |
| 1063 | 0 | break; |
| 1064 | case 'm': | |
| 1065 | 0 | if (this.rotAt) { |
| 1066 | 0 | rotAt = Date.UTC(d.getUTCFullYear(), |
| 1067 | d.getUTCMonth() + this.periodNum, 1); | |
| 1068 | } else { | |
| 1069 | // First time: the start of the next month. | |
| 1070 | 0 | rotAt = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1); |
| 1071 | } | |
| 1072 | 0 | break; |
| 1073 | case 'y': | |
| 1074 | 0 | if (this.rotAt) { |
| 1075 | 0 | rotAt = Date.UTC(d.getUTCFullYear() + this.periodNum, 0, 1); |
| 1076 | } else { | |
| 1077 | // First time: the start of the next year. | |
| 1078 | 0 | rotAt = Date.UTC(d.getUTCFullYear() + 1, 0, 1); |
| 1079 | } | |
| 1080 | 0 | break; |
| 1081 | default: | |
| 1082 | 0 | assert.fail(format('invalid period scope: "%s"', this.periodScope)); |
| 1083 | } | |
| 1084 | ||
| 1085 | 0 | if (_DEBUG) { |
| 1086 | 0 | console.log(' **rotAt**: %s (utc: %s)', rotAt, |
| 1087 | new Date(rotAt).toUTCString()); | |
| 1088 | 0 | var now = Date.now(); |
| 1089 | 0 | console.log(' now: %s (%sms == %smin == %sh to go)', |
| 1090 | now, | |
| 1091 | rotAt - now, | |
| 1092 | (rotAt-now)/1000/60, | |
| 1093 | (rotAt-now)/1000/60/60); | |
| 1094 | } | |
| 1095 | 0 | return rotAt; |
| 1096 | }; | |
| 1097 | ||
| 1098 | 1 | RotatingFileStream.prototype.rotate = function rotate() { |
| 1099 | // XXX What about shutdown? | |
| 1100 | 0 | var self = this; |
| 1101 | 0 | var _DEBUG = false; |
| 1102 | ||
| 1103 | 0 | if (_DEBUG) console.log('-- [%s] rotating %s', new Date(), self.path); |
| 1104 | 0 | if (self.rotating) { |
| 1105 | 0 | throw new TypeError('cannot start a rotation when already rotating'); |
| 1106 | } | |
| 1107 | 0 | self.rotating = true; |
| 1108 | ||
| 1109 | 0 | self.stream.end(); // XXX can do moves sync after this? test at high rate |
| 1110 | ||
| 1111 | 0 | function del() { |
| 1112 | 0 | var toDel = self.path + '.' + String(n - 1); |
| 1113 | 0 | if (n === 0) { |
| 1114 | 0 | toDel = self.path; |
| 1115 | } | |
| 1116 | 0 | n -= 1; |
| 1117 | 0 | if (_DEBUG) console.log('rm %s', toDel); |
| 1118 | 0 | fs.unlink(toDel, function (delErr) { |
| 1119 | //XXX handle err other than not exists | |
| 1120 | 0 | moves(); |
| 1121 | }); | |
| 1122 | } | |
| 1123 | ||
| 1124 | 0 | function moves() { |
| 1125 | 0 | if (self.count === 0 || n < 0) { |
| 1126 | 0 | return finish(); |
| 1127 | } | |
| 1128 | 0 | var before = self.path; |
| 1129 | 0 | var after = self.path + '.' + String(n); |
| 1130 | 0 | if (n > 0) { |
| 1131 | 0 | before += '.' + String(n - 1); |
| 1132 | } | |
| 1133 | 0 | n -= 1; |
| 1134 | 0 | fs.exists(before, function (exists) { |
| 1135 | 0 | if (!exists) { |
| 1136 | 0 | moves(); |
| 1137 | } else { | |
| 1138 | 0 | if (_DEBUG) console.log('mv %s %s', before, after); |
| 1139 | 0 | mv(before, after, function (mvErr) { |
| 1140 | 0 | if (mvErr) { |
| 1141 | 0 | self.emit('error', mvErr); |
| 1142 | 0 | finish(); // XXX finish here? |
| 1143 | } else { | |
| 1144 | 0 | moves(); |
| 1145 | } | |
| 1146 | }); | |
| 1147 | } | |
| 1148 | }) | |
| 1149 | } | |
| 1150 | ||
| 1151 | 0 | function finish() { |
| 1152 | 0 | if (_DEBUG) console.log('open %s', self.path); |
| 1153 | 0 | self.stream = fs.createWriteStream(self.path, |
| 1154 | {flags: 'a', encoding: 'utf8'}); | |
| 1155 | 0 | var q = self.rotQueue, len = q.length; |
| 1156 | 0 | for (var i = 0; i < len; i++) { |
| 1157 | 0 | self.stream.write(q[i]); |
| 1158 | } | |
| 1159 | 0 | self.rotQueue = []; |
| 1160 | 0 | self.rotating = false; |
| 1161 | 0 | self.emit('drain'); |
| 1162 | 0 | self._setupNextRot(); |
| 1163 | } | |
| 1164 | ||
| 1165 | 0 | var n = this.count; |
| 1166 | 0 | del(); |
| 1167 | }; | |
| 1168 | ||
| 1169 | 1 | RotatingFileStream.prototype.write = function write(s) { |
| 1170 | 0 | if (this.rotating) { |
| 1171 | 0 | this.rotQueue.push(s); |
| 1172 | 0 | return false; |
| 1173 | } else { | |
| 1174 | 0 | return this.stream.write(s); |
| 1175 | } | |
| 1176 | }; | |
| 1177 | ||
| 1178 | 1 | RotatingFileStream.prototype.end = function end(s) { |
| 1179 | 0 | this.stream.end(); |
| 1180 | }; | |
| 1181 | ||
| 1182 | 1 | RotatingFileStream.prototype.destroy = function destroy(s) { |
| 1183 | 0 | this.stream.destroy(); |
| 1184 | }; | |
| 1185 | ||
| 1186 | 1 | RotatingFileStream.prototype.destroySoon = function destroySoon(s) { |
| 1187 | 0 | this.stream.destroySoon(); |
| 1188 | }; | |
| 1189 | ||
| 1190 | } /* if (mv) */ | |
| 1191 | ||
| 1192 | ||
| 1193 | ||
| 1194 | /** | |
| 1195 | * RingBuffer is a Writable Stream that just stores the last N records in | |
| 1196 | * memory. | |
| 1197 | * | |
| 1198 | * @param options {Object}, with the following fields: | |
| 1199 | * | |
| 1200 | * - limit: number of records to keep in memory | |
| 1201 | */ | |
| 1202 | 1 | function RingBuffer(options) { |
| 1203 | 0 | this.limit = options && options.limit ? options.limit : 100; |
| 1204 | 0 | this.writable = true; |
| 1205 | 0 | this.records = []; |
| 1206 | 0 | EventEmitter.call(this); |
| 1207 | } | |
| 1208 | ||
| 1209 | 1 | util.inherits(RingBuffer, EventEmitter); |
| 1210 | ||
| 1211 | 1 | RingBuffer.prototype.write = function (record) { |
| 1212 | 0 | if (!this.writable) |
| 1213 | 0 | throw (new Error('RingBuffer has been ended already')); |
| 1214 | ||
| 1215 | 0 | this.records.push(record); |
| 1216 | ||
| 1217 | 0 | if (this.records.length > this.limit) |
| 1218 | 0 | this.records.shift(); |
| 1219 | ||
| 1220 | 0 | return (true); |
| 1221 | }; | |
| 1222 | ||
| 1223 | 1 | RingBuffer.prototype.end = function () { |
| 1224 | 0 | if (arguments.length > 0) |
| 1225 | 0 | this.write.apply(this, Array.prototype.slice.call(arguments)); |
| 1226 | 0 | this.writable = false; |
| 1227 | }; | |
| 1228 | ||
| 1229 | 1 | RingBuffer.prototype.destroy = function () { |
| 1230 | 0 | this.writable = false; |
| 1231 | 0 | this.emit('close'); |
| 1232 | }; | |
| 1233 | ||
| 1234 | 1 | RingBuffer.prototype.destroySoon = function () { |
| 1235 | 0 | this.destroy(); |
| 1236 | }; | |
| 1237 | ||
| 1238 | ||
| 1239 | //---- Exports | |
| 1240 | ||
| 1241 | 1 | module.exports = Logger; |
| 1242 | ||
| 1243 | 1 | module.exports.TRACE = TRACE; |
| 1244 | 1 | module.exports.DEBUG = DEBUG; |
| 1245 | 1 | module.exports.INFO = INFO; |
| 1246 | 1 | module.exports.WARN = WARN; |
| 1247 | 1 | module.exports.ERROR = ERROR; |
| 1248 | 1 | module.exports.FATAL = FATAL; |
| 1249 | 1 | module.exports.resolveLevel = resolveLevel; |
| 1250 | ||
| 1251 | 1 | module.exports.VERSION = VERSION; |
| 1252 | 1 | module.exports.LOG_VERSION = LOG_VERSION; |
| 1253 | ||
| 1254 | 1 | module.exports.createLogger = function createLogger(options) { |
| 1255 | 1 | return new Logger(options); |
| 1256 | }; | |
| 1257 | ||
| 1258 | 1 | module.exports.RingBuffer = RingBuffer; |
| 1259 | ||
| 1260 | // Useful for custom `type == 'raw'` streams that may do JSON stringification | |
| 1261 | // of log records themselves. Usage: | |
| 1262 | // var str = JSON.stringify(rec, bunyan.safeCycles()); | |
| 1263 | 1 | module.exports.safeCycles = safeCycles; |
| 1264 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | var Collection = require('./collection').Collection, |
| 5 | Cursor = require('./cursor').Cursor, | |
| 6 | DbCommand = require('./commands/db_command').DbCommand, | |
| 7 | utils = require('./utils'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Allows the user to access the admin functionality of MongoDB | |
| 11 | * | |
| 12 | * @class Represents the Admin methods of MongoDB. | |
| 13 | * @param {Object} db Current db instance we wish to perform Admin operations on. | |
| 14 | * @return {Function} Constructor for Admin type. | |
| 15 | */ | |
| 16 | 1 | function Admin(db) { |
| 17 | 0 | if(!(this instanceof Admin)) return new Admin(db); |
| 18 | 0 | this.db = db; |
| 19 | }; | |
| 20 | ||
| 21 | /** | |
| 22 | * Retrieve the server information for the current | |
| 23 | * instance of the db client | |
| 24 | * | |
| 25 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from buildInfo or null if an error occured. | |
| 26 | * @return {null} Returns no result | |
| 27 | * @api public | |
| 28 | */ | |
| 29 | 1 | Admin.prototype.buildInfo = function(callback) { |
| 30 | 0 | this.serverInfo(callback); |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| 34 | * Retrieve the server information for the current | |
| 35 | * instance of the db client | |
| 36 | * | |
| 37 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverInfo or null if an error occured. | |
| 38 | * @return {null} Returns no result | |
| 39 | * @api private | |
| 40 | */ | |
| 41 | 1 | Admin.prototype.serverInfo = function(callback) { |
| 42 | 0 | this.db.executeDbAdminCommand({buildinfo:1}, function(err, doc) { |
| 43 | 0 | if(err != null) return callback(err, null); |
| 44 | 0 | return callback(null, doc.documents[0]); |
| 45 | }); | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Retrieve this db's server status. | |
| 50 | * | |
| 51 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverStatus or null if an error occured. | |
| 52 | * @return {null} | |
| 53 | * @api public | |
| 54 | */ | |
| 55 | 1 | Admin.prototype.serverStatus = function(callback) { |
| 56 | 0 | var self = this; |
| 57 | ||
| 58 | 0 | this.db.executeDbAdminCommand({serverStatus: 1}, function(err, doc) { |
| 59 | 0 | if(err == null && doc.documents[0].ok === 1) { |
| 60 | 0 | callback(null, doc.documents[0]); |
| 61 | } else { | |
| 62 | 0 | if(err) return callback(err, false); |
| 63 | 0 | return callback(utils.toError(doc.documents[0]), false); |
| 64 | } | |
| 65 | }); | |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Retrieve the current profiling Level for MongoDB | |
| 70 | * | |
| 71 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingLevel or null if an error occured. | |
| 72 | * @return {null} Returns no result | |
| 73 | * @api public | |
| 74 | */ | |
| 75 | 1 | Admin.prototype.profilingLevel = function(callback) { |
| 76 | 0 | var self = this; |
| 77 | ||
| 78 | 0 | this.db.executeDbAdminCommand({profile:-1}, function(err, doc) { |
| 79 | 0 | doc = doc.documents[0]; |
| 80 | ||
| 81 | 0 | if(err == null && doc.ok === 1) { |
| 82 | 0 | var was = doc.was; |
| 83 | 0 | if(was == 0) return callback(null, "off"); |
| 84 | 0 | if(was == 1) return callback(null, "slow_only"); |
| 85 | 0 | if(was == 2) return callback(null, "all"); |
| 86 | 0 | return callback(new Error("Error: illegal profiling level value " + was), null); |
| 87 | } else { | |
| 88 | 0 | err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); |
| 89 | } | |
| 90 | }); | |
| 91 | }; | |
| 92 | ||
| 93 | /** | |
| 94 | * Ping the MongoDB server and retrieve results | |
| 95 | * | |
| 96 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ping or null if an error occured. | |
| 97 | * @return {null} Returns no result | |
| 98 | * @api public | |
| 99 | */ | |
| 100 | 1 | Admin.prototype.ping = function(options, callback) { |
| 101 | // Unpack calls | |
| 102 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 103 | 0 | callback = args.pop(); |
| 104 | ||
| 105 | 0 | this.db.executeDbAdminCommand({ping: 1}, callback); |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Authenticate against MongoDB | |
| 110 | * | |
| 111 | * @param {String} username The user name for the authentication. | |
| 112 | * @param {String} password The password for the authentication. | |
| 113 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authenticate or null if an error occured. | |
| 114 | * @return {null} Returns no result | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | Admin.prototype.authenticate = function(username, password, callback) { |
| 118 | 0 | this.db.authenticate(username, password, {authdb: 'admin'}, function(err, doc) { |
| 119 | 0 | return callback(err, doc); |
| 120 | }) | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Logout current authenticated user | |
| 125 | * | |
| 126 | * @param {Object} [options] Optional parameters to the command. | |
| 127 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured. | |
| 128 | * @return {null} Returns no result | |
| 129 | * @api public | |
| 130 | */ | |
| 131 | 1 | Admin.prototype.logout = function(callback) { |
| 132 | 0 | this.db.logout({authdb: 'admin'}, function(err, doc) { |
| 133 | 0 | return callback(err, doc); |
| 134 | }) | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Add a user to the MongoDB server, if the user exists it will | |
| 139 | * overwrite the current password | |
| 140 | * | |
| 141 | * Options | |
| 142 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 143 | * | |
| 144 | * @param {String} username The user name for the authentication. | |
| 145 | * @param {String} password The password for the authentication. | |
| 146 | * @param {Object} [options] additional options during update. | |
| 147 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured. | |
| 148 | * @return {null} Returns no result | |
| 149 | * @api public | |
| 150 | */ | |
| 151 | 1 | Admin.prototype.addUser = function(username, password, options, callback) { |
| 152 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 153 | 0 | callback = args.pop(); |
| 154 | 0 | options = args.length ? args.shift() : {}; |
| 155 | // Set the db name to admin | |
| 156 | 0 | options.dbName = 'admin'; |
| 157 | // Add user | |
| 158 | 0 | this.db.addUser(username, password, options, function(err, doc) { |
| 159 | 0 | return callback(err, doc); |
| 160 | }) | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Remove a user from the MongoDB server | |
| 165 | * | |
| 166 | * Options | |
| 167 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 168 | * | |
| 169 | * @param {String} username The user name for the authentication. | |
| 170 | * @param {Object} [options] additional options during update. | |
| 171 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured. | |
| 172 | * @return {null} Returns no result | |
| 173 | * @api public | |
| 174 | */ | |
| 175 | 1 | Admin.prototype.removeUser = function(username, options, callback) { |
| 176 | 0 | var self = this; |
| 177 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 178 | 0 | callback = args.pop(); |
| 179 | 0 | options = args.length ? args.shift() : {}; |
| 180 | 0 | options.dbName = 'admin'; |
| 181 | ||
| 182 | 0 | this.db.removeUser(username, options, function(err, doc) { |
| 183 | 0 | return callback(err, doc); |
| 184 | }) | |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Set the current profiling level of MongoDB | |
| 189 | * | |
| 190 | * @param {String} level The new profiling level (off, slow_only, all) | |
| 191 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from setProfilingLevel or null if an error occured. | |
| 192 | * @return {null} Returns no result | |
| 193 | * @api public | |
| 194 | */ | |
| 195 | 1 | Admin.prototype.setProfilingLevel = function(level, callback) { |
| 196 | 0 | var self = this; |
| 197 | 0 | var command = {}; |
| 198 | 0 | var profile = 0; |
| 199 | ||
| 200 | 0 | if(level == "off") { |
| 201 | 0 | profile = 0; |
| 202 | 0 | } else if(level == "slow_only") { |
| 203 | 0 | profile = 1; |
| 204 | 0 | } else if(level == "all") { |
| 205 | 0 | profile = 2; |
| 206 | } else { | |
| 207 | 0 | return callback(new Error("Error: illegal profiling level value " + level)); |
| 208 | } | |
| 209 | ||
| 210 | // Set up the profile number | |
| 211 | 0 | command['profile'] = profile; |
| 212 | ||
| 213 | 0 | this.db.executeDbAdminCommand(command, function(err, doc) { |
| 214 | 0 | doc = doc.documents[0]; |
| 215 | ||
| 216 | 0 | if(err == null && doc.ok === 1) |
| 217 | 0 | return callback(null, level); |
| 218 | 0 | return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); |
| 219 | }); | |
| 220 | }; | |
| 221 | ||
| 222 | /** | |
| 223 | * Retrive the current profiling information for MongoDB | |
| 224 | * | |
| 225 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingInfo or null if an error occured. | |
| 226 | * @return {null} Returns no result | |
| 227 | * @api public | |
| 228 | */ | |
| 229 | 1 | Admin.prototype.profilingInfo = function(callback) { |
| 230 | 0 | try { |
| 231 | 0 | new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}, {}, {dbName: 'admin'}).toArray(function(err, items) { |
| 232 | 0 | return callback(err, items); |
| 233 | }); | |
| 234 | } catch (err) { | |
| 235 | 0 | return callback(err, null); |
| 236 | } | |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Execute a db command against the Admin database | |
| 241 | * | |
| 242 | * @param {Object} command A command object `{ping:1}`. | |
| 243 | * @param {Object} [options] Optional parameters to the command. | |
| 244 | * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. | |
| 245 | * @return {null} Returns no result | |
| 246 | * @api public | |
| 247 | */ | |
| 248 | 1 | Admin.prototype.command = function(command, options, callback) { |
| 249 | 0 | var self = this; |
| 250 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 251 | 0 | callback = args.pop(); |
| 252 | 0 | options = args.length ? args.shift() : {}; |
| 253 | ||
| 254 | // Execute a command | |
| 255 | 0 | this.db.executeDbAdminCommand(command, options, function(err, doc) { |
| 256 | // Ensure change before event loop executes | |
| 257 | 0 | return callback != null ? callback(err, doc) : null; |
| 258 | }); | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Validate an existing collection | |
| 263 | * | |
| 264 | * @param {String} collectionName The name of the collection to validate. | |
| 265 | * @param {Object} [options] Optional parameters to the command. | |
| 266 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from validateCollection or null if an error occured. | |
| 267 | * @return {null} Returns no result | |
| 268 | * @api public | |
| 269 | */ | |
| 270 | 1 | Admin.prototype.validateCollection = function(collectionName, options, callback) { |
| 271 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 272 | 0 | callback = args.pop(); |
| 273 | 0 | options = args.length ? args.shift() : {}; |
| 274 | ||
| 275 | 0 | var self = this; |
| 276 | 0 | var command = {validate: collectionName}; |
| 277 | 0 | var keys = Object.keys(options); |
| 278 | ||
| 279 | // Decorate command with extra options | |
| 280 | 0 | for(var i = 0; i < keys.length; i++) { |
| 281 | 0 | if(options.hasOwnProperty(keys[i])) { |
| 282 | 0 | command[keys[i]] = options[keys[i]]; |
| 283 | } | |
| 284 | } | |
| 285 | ||
| 286 | 0 | this.db.executeDbCommand(command, function(err, doc) { |
| 287 | 0 | if(err != null) return callback(err, null); |
| 288 | 0 | doc = doc.documents[0]; |
| 289 | ||
| 290 | 0 | if(doc.ok === 0) |
| 291 | 0 | return callback(new Error("Error with validate command"), null); |
| 292 | 0 | if(doc.result != null && doc.result.constructor != String) |
| 293 | 0 | return callback(new Error("Error with validation data"), null); |
| 294 | 0 | if(doc.result != null && doc.result.match(/exception|corrupt/) != null) |
| 295 | 0 | return callback(new Error("Error: invalid collection " + collectionName), null); |
| 296 | 0 | if(doc.valid != null && !doc.valid) |
| 297 | 0 | return callback(new Error("Error: invalid collection " + collectionName), null); |
| 298 | ||
| 299 | 0 | return callback(null, doc); |
| 300 | }); | |
| 301 | }; | |
| 302 | ||
| 303 | /** | |
| 304 | * List the available databases | |
| 305 | * | |
| 306 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from listDatabases or null if an error occured. | |
| 307 | * @return {null} Returns no result | |
| 308 | * @api public | |
| 309 | */ | |
| 310 | 1 | Admin.prototype.listDatabases = function(callback) { |
| 311 | // Execute the listAllDatabases command | |
| 312 | 0 | this.db.executeDbAdminCommand({listDatabases:1}, {}, function(err, doc) { |
| 313 | 0 | if(err != null) return callback(err, null); |
| 314 | 0 | return callback(null, doc.documents[0]); |
| 315 | }); | |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * Get ReplicaSet status | |
| 320 | * | |
| 321 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from replSetGetStatus or null if an error occured. | |
| 322 | * @return {null} | |
| 323 | * @api public | |
| 324 | */ | |
| 325 | 1 | Admin.prototype.replSetGetStatus = function(callback) { |
| 326 | 0 | var self = this; |
| 327 | ||
| 328 | 0 | this.db.executeDbAdminCommand({replSetGetStatus:1}, function(err, doc) { |
| 329 | 0 | if(err == null && doc.documents[0].ok === 1) |
| 330 | 0 | return callback(null, doc.documents[0]); |
| 331 | 0 | if(err) return callback(err, false); |
| 332 | 0 | return callback(utils.toError(doc.documents[0]), false); |
| 333 | }); | |
| 334 | }; | |
| 335 | ||
| 336 | /** | |
| 337 | * @ignore | |
| 338 | */ | |
| 339 | 1 | exports.Admin = Admin; |
| 340 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('./connection/read_preference').ReadPreference |
| 2 | , Readable = require('stream').Readable | |
| 3 | , CommandCursor = require('./command_cursor').CommandCursor | |
| 4 | , utils = require('./utils') | |
| 5 | , shared = require('./collection/shared') | |
| 6 | , inherits = require('util').inherits; | |
| 7 | ||
| 8 | 1 | var AggregationCursor = function(collection, serverCapabilities, options) { |
| 9 | 0 | var pipe = []; |
| 10 | 0 | var self = this; |
| 11 | 0 | var results = null; |
| 12 | 0 | var _cursor_options = {}; |
| 13 | // Ensure we have options set up | |
| 14 | 0 | options = options == null ? {} : options; |
| 15 | ||
| 16 | // If a pipeline was provided | |
| 17 | 0 | pipe = Array.isArray(options.pipe) ? options.pipe : pipe; |
| 18 | // Set passed in batchSize if provided | |
| 19 | 0 | if(typeof options.batchSize == 'number') _cursor_options.batchSize = options.batchSize; |
| 20 | // Get the read Preference | |
| 21 | 0 | var readPreference = shared._getReadConcern(collection, options); |
| 22 | ||
| 23 | // Set up | |
| 24 | 0 | Readable.call(this, {objectMode: true}); |
| 25 | ||
| 26 | // Contains connection | |
| 27 | 0 | var connection = null; |
| 28 | ||
| 29 | // Set the read preference | |
| 30 | 0 | var _options = { |
| 31 | readPreference: readPreference | |
| 32 | }; | |
| 33 | ||
| 34 | // Actual command | |
| 35 | 0 | var command = { |
| 36 | aggregate: collection.collectionName | |
| 37 | , pipeline: pipe | |
| 38 | , cursor: _cursor_options | |
| 39 | } | |
| 40 | ||
| 41 | // If allowDiskUsage is set | |
| 42 | 0 | if(typeof options.allowDiskUsage == 'boolean') |
| 43 | 0 | command.allowDiskUsage = options.allowDiskUsage; |
| 44 | ||
| 45 | // Command cursor (if we support one) | |
| 46 | 0 | var commandCursor = new CommandCursor(collection.db, collection, command); |
| 47 | ||
| 48 | // // Internal cursor methods | |
| 49 | // this.find = function(selector) { | |
| 50 | // pipe.push({$match: selector}); | |
| 51 | // return self; | |
| 52 | // } | |
| 53 | ||
| 54 | // this.unwind = function(unwind) { | |
| 55 | // pipe.push({$unwind: unwind}); | |
| 56 | // return self; | |
| 57 | // } | |
| 58 | ||
| 59 | // this.group = function(group) { | |
| 60 | // pipe.push({$group: group}); | |
| 61 | // return self; | |
| 62 | // } | |
| 63 | ||
| 64 | // this.project = function(project) { | |
| 65 | // pipe.push({$project: project}); | |
| 66 | // return self; | |
| 67 | // } | |
| 68 | ||
| 69 | // this.limit = function(limit) { | |
| 70 | // pipe.push({$limit: limit}); | |
| 71 | // return self; | |
| 72 | // } | |
| 73 | ||
| 74 | // this.geoNear = function(geoNear) { | |
| 75 | // pipe.push({$geoNear: geoNear}); | |
| 76 | // return self; | |
| 77 | // } | |
| 78 | ||
| 79 | // this.sort = function(sort) { | |
| 80 | // pipe.push({$sort: sort}); | |
| 81 | // return self; | |
| 82 | // } | |
| 83 | ||
| 84 | // this.withReadPreference = function(read_preference) { | |
| 85 | // _options.readPreference = read_preference; | |
| 86 | // return self; | |
| 87 | // } | |
| 88 | ||
| 89 | // this.withQueryOptions = function(options) { | |
| 90 | // if(options.batchSize) { | |
| 91 | // _cursor_options.batchSize = options.batchSize; | |
| 92 | // } | |
| 93 | ||
| 94 | // // Return the cursor | |
| 95 | // return self; | |
| 96 | // } | |
| 97 | ||
| 98 | // this.skip = function(skip) { | |
| 99 | // pipe.push({$skip: skip}); | |
| 100 | // return self; | |
| 101 | // } | |
| 102 | ||
| 103 | // this.allowDiskUsage = function(allowDiskUsage) { | |
| 104 | // command.allowDiskUsage = allowDiskUsage; | |
| 105 | // return self; | |
| 106 | // } | |
| 107 | ||
| 108 | 0 | this.explain = function(callback) { |
| 109 | 0 | if(typeof callback != 'function') |
| 110 | 0 | throw utils.toError("AggregationCursor explain requires a callback function"); |
| 111 | ||
| 112 | // Add explain options | |
| 113 | 0 | _options.explain = true; |
| 114 | // Execute aggregation pipeline | |
| 115 | 0 | collection.aggregate(pipe, _options, function(err, results) { |
| 116 | 0 | if(err) return callback(err, null); |
| 117 | 0 | callback(null, results); |
| 118 | }); | |
| 119 | } | |
| 120 | ||
| 121 | // this.maxTimeMS = function(maxTimeMS) { | |
| 122 | // if(typeof maxTimeMS != 'number') { | |
| 123 | // throw new Error("maxTimeMS must be a number"); | |
| 124 | // } | |
| 125 | ||
| 126 | // // Save the maxTimeMS | |
| 127 | // _options.maxTimeMS = maxTimeMS | |
| 128 | // // Set the maxTimeMS on the command cursor | |
| 129 | // commandCursor.maxTimeMS(maxTimeMS); | |
| 130 | // return self; | |
| 131 | // } | |
| 132 | ||
| 133 | 0 | this.get = function(callback) { |
| 134 | 0 | if(typeof callback != 'function') |
| 135 | 0 | throw utils.toError("AggregationCursor get requires a callback function"); |
| 136 | // Checkout a connection | |
| 137 | 0 | var _connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 138 | // Fall back | |
| 139 | 0 | if(!_connection.serverCapabilities.hasAggregationCursor) { |
| 140 | 0 | return collection.aggregate(pipe, _options, function(err, results) { |
| 141 | 0 | if(err) return callback(err); |
| 142 | 0 | callback(null, results); |
| 143 | }); | |
| 144 | } | |
| 145 | ||
| 146 | // Execute get using command Cursor | |
| 147 | 0 | commandCursor.get({connection: _connection}, callback); |
| 148 | } | |
| 149 | ||
| 150 | 0 | this.getOne = function(callback) { |
| 151 | 0 | if(typeof callback != 'function') |
| 152 | 0 | throw utils.toError("AggregationCursor getOne requires a callback function"); |
| 153 | // Set the limit to 1 | |
| 154 | 0 | pipe.push({$limit: 1}); |
| 155 | // For now we have no cursor command so let's just wrap existing results | |
| 156 | 0 | collection.aggregate(pipe, _options, function(err, results) { |
| 157 | 0 | if(err) return callback(err); |
| 158 | 0 | callback(null, results[0]); |
| 159 | }); | |
| 160 | } | |
| 161 | ||
| 162 | 0 | this.each = function(callback) { |
| 163 | // Checkout a connection if we have none | |
| 164 | 0 | if(!connection) |
| 165 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 166 | ||
| 167 | // Fall back | |
| 168 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 169 | 0 | collection.aggregate(pipe, _options, function(err, _results) { |
| 170 | 0 | if(err) return callback(err); |
| 171 | ||
| 172 | 0 | while(_results.length > 0) { |
| 173 | 0 | callback(null, _results.shift()); |
| 174 | } | |
| 175 | ||
| 176 | 0 | callback(null, null); |
| 177 | }); | |
| 178 | } | |
| 179 | ||
| 180 | // Execute each using command Cursor | |
| 181 | 0 | commandCursor.each({connection: connection}, callback); |
| 182 | } | |
| 183 | ||
| 184 | 0 | this.next = function(callback) { |
| 185 | 0 | if(typeof callback != 'function') |
| 186 | 0 | throw utils.toError("AggregationCursor next requires a callback function"); |
| 187 | ||
| 188 | // Checkout a connection if we have none | |
| 189 | 0 | if(!connection) |
| 190 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 191 | ||
| 192 | // Fall back | |
| 193 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 194 | 0 | if(!results) { |
| 195 | // For now we have no cursor command so let's just wrap existing results | |
| 196 | 0 | return collection.aggregate(pipe, _options, function(err, _results) { |
| 197 | 0 | if(err) return callback(err); |
| 198 | 0 | results = _results; |
| 199 | ||
| 200 | // Ensure we don't issue undefined | |
| 201 | 0 | var item = results.shift(); |
| 202 | 0 | callback(null, item ? item : null); |
| 203 | }); | |
| 204 | } | |
| 205 | ||
| 206 | // Ensure we don't issue undefined | |
| 207 | 0 | var item = results.shift(); |
| 208 | // Return the item | |
| 209 | 0 | return callback(null, item ? item : null); |
| 210 | } | |
| 211 | ||
| 212 | // Execute next using command Cursor | |
| 213 | 0 | commandCursor.next({connection: connection}, callback); |
| 214 | } | |
| 215 | ||
| 216 | // | |
| 217 | // Close method | |
| 218 | // | |
| 219 | 0 | this.close = function(callback) { |
| 220 | 0 | if(typeof callback != 'function') |
| 221 | 0 | throw utils.toError("AggregationCursor close requires a callback function"); |
| 222 | ||
| 223 | // Checkout a connection if we have none | |
| 224 | 0 | if(!connection) |
| 225 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 226 | ||
| 227 | // Fall back | |
| 228 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 229 | 0 | return callback(null, null); |
| 230 | } | |
| 231 | ||
| 232 | // Execute next using command Cursor | |
| 233 | 0 | commandCursor.close({connection: connection}, callback); |
| 234 | } | |
| 235 | ||
| 236 | // | |
| 237 | // Stream method | |
| 238 | // | |
| 239 | 0 | this._read = function(n) { |
| 240 | 0 | self.next(function(err, result) { |
| 241 | 0 | if(err) { |
| 242 | 0 | self.emit('error', err); |
| 243 | 0 | return self.push(null); |
| 244 | } | |
| 245 | ||
| 246 | 0 | self.push(result); |
| 247 | }); | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | // Inherit from Readable | |
| 252 | 1 | if(Readable != null) { |
| 253 | 1 | inherits(AggregationCursor, Readable); |
| 254 | } | |
| 255 | ||
| 256 | // Exports the Aggregation Framework | |
| 257 | 1 | exports.AggregationCursor = AggregationCursor; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils'); | |
| 3 | ||
| 4 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 5 | 0 | var numberOfConnections = 0; |
| 6 | 0 | var errorObject = null; |
| 7 | ||
| 8 | 0 | if(options['connection'] != null) { |
| 9 | //if a connection was explicitly passed on options, then we have only one... | |
| 10 | 0 | numberOfConnections = 1; |
| 11 | } else { | |
| 12 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 13 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 14 | 0 | options['onAll'] = true; |
| 15 | } | |
| 16 | ||
| 17 | // Execute all four | |
| 18 | 0 | db._executeQueryCommand(DbCommand.createGetNonceCommand(db), options, function(err, result, connection) { |
| 19 | // console.log("--------------------------------------------- MONGODB-CR 0") | |
| 20 | // console.dir(err) | |
| 21 | // Execute on all the connections | |
| 22 | 0 | if(err == null) { |
| 23 | // Nonce used to make authentication request with md5 hash | |
| 24 | 0 | var nonce = result.documents[0].nonce; |
| 25 | // Execute command | |
| 26 | 0 | db._executeQueryCommand(DbCommand.createAuthenticationCommand(db, username, password, nonce, authdb), {connection:connection}, function(err, result) { |
| 27 | // console.log("--------------------------------------------- MONGODB-CR 1") | |
| 28 | // console.dir(err) | |
| 29 | // console.dir(result) | |
| 30 | // Count down | |
| 31 | 0 | numberOfConnections = numberOfConnections - 1; |
| 32 | // Ensure we save any error | |
| 33 | 0 | if(err) { |
| 34 | 0 | errorObject = err; |
| 35 | 0 | } else if(result |
| 36 | && Array.isArray(result.documents) | |
| 37 | && result.documents.length > 0 | |
| 38 | && (result.documents[0].err != null || result.documents[0].errmsg != null)) { | |
| 39 | 0 | errorObject = utils.toError(result.documents[0]); |
| 40 | } | |
| 41 | ||
| 42 | // Work around the case where the number of connections are 0 | |
| 43 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 44 | 0 | var internalCallback = callback; |
| 45 | 0 | callback = null; |
| 46 | ||
| 47 | 0 | if(errorObject == null |
| 48 | && result && Array.isArray(result.documents) && result.documents.length > 0 | |
| 49 | && result.documents[0].ok == 1) { // We authenticated correctly save the credentials | |
| 50 | 0 | db.serverConfig.auth.add('MONGODB-CR', db.databaseName, username, password, authdb); |
| 51 | // Return callback | |
| 52 | 0 | internalCallback(errorObject, true); |
| 53 | } else { | |
| 54 | 0 | internalCallback(errorObject, false); |
| 55 | } | |
| 56 | } | |
| 57 | }); | |
| 58 | } | |
| 59 | }); | |
| 60 | } | |
| 61 | ||
| 62 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , format = require('util').format; | |
| 4 | ||
| 5 | // Kerberos class | |
| 6 | 1 | var Kerberos = null; |
| 7 | 1 | var MongoAuthProcess = null; |
| 8 | // Try to grab the Kerberos class | |
| 9 | 1 | try { |
| 10 | 1 | Kerberos = require('kerberos').Kerberos |
| 11 | // Authentication process for Mongo | |
| 12 | 1 | MongoAuthProcess = require('kerberos').processes.MongoAuthProcess |
| 13 | } catch(err) {} | |
| 14 | ||
| 15 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 16 | 0 | var numberOfConnections = 0; |
| 17 | 0 | var errorObject = null; |
| 18 | // We don't have the Kerberos library | |
| 19 | 0 | if(Kerberos == null) return callback(new Error("Kerberos library is not installed")); |
| 20 | ||
| 21 | 0 | if(options['connection'] != null) { |
| 22 | //if a connection was explicitly passed on options, then we have only one... | |
| 23 | 0 | numberOfConnections = 1; |
| 24 | } else { | |
| 25 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 26 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 27 | 0 | options['onAll'] = true; |
| 28 | } | |
| 29 | ||
| 30 | // Grab all the connections | |
| 31 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 32 | 0 | var gssapiServiceName = options['gssapiServiceName'] || 'mongodb'; |
| 33 | 0 | var error = null; |
| 34 | // Authenticate all connections | |
| 35 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 36 | ||
| 37 | // Start Auth process for a connection | |
| 38 | 0 | GSSAPIInitialize(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) { |
| 39 | // Adjust number of connections left to connect | |
| 40 | 0 | numberOfConnections = numberOfConnections - 1; |
| 41 | // If we have an error save it | |
| 42 | 0 | if(err) error = err; |
| 43 | ||
| 44 | // We are done | |
| 45 | 0 | if(numberOfConnections == 0) { |
| 46 | 0 | if(err) return callback(error, false); |
| 47 | // We authenticated correctly save the credentials | |
| 48 | 0 | db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName); |
| 49 | // Return valid callback | |
| 50 | 0 | return callback(null, true); |
| 51 | } | |
| 52 | }); | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | // | |
| 57 | // Initialize step | |
| 58 | 1 | var GSSAPIInitialize = function(db, username, password, authdb, gssapiServiceName, connection, callback) { |
| 59 | // Create authenticator | |
| 60 | 0 | var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, gssapiServiceName); |
| 61 | ||
| 62 | // Perform initialization | |
| 63 | 0 | mongo_auth_process.init(username, password, function(err, context) { |
| 64 | 0 | if(err) return callback(err, false); |
| 65 | ||
| 66 | // Perform the first step | |
| 67 | 0 | mongo_auth_process.transition('', function(err, payload) { |
| 68 | 0 | if(err) return callback(err, false); |
| 69 | ||
| 70 | // Call the next db step | |
| 71 | 0 | MongoDBGSSAPIFirstStep(mongo_auth_process, payload, db, username, password, authdb, connection, callback); |
| 72 | }); | |
| 73 | }); | |
| 74 | } | |
| 75 | ||
| 76 | // | |
| 77 | // Perform first step against mongodb | |
| 78 | 1 | var MongoDBGSSAPIFirstStep = function(mongo_auth_process, payload, db, username, password, authdb, connection, callback) { |
| 79 | // Build the sasl start command | |
| 80 | 0 | var command = { |
| 81 | saslStart: 1 | |
| 82 | , mechanism: 'GSSAPI' | |
| 83 | , payload: payload | |
| 84 | , autoAuthorize: 1 | |
| 85 | }; | |
| 86 | ||
| 87 | // Execute first sasl step | |
| 88 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 89 | 0 | if(err) return callback(err, false); |
| 90 | // Get the payload | |
| 91 | 0 | doc = doc.documents[0]; |
| 92 | 0 | var db_payload = doc.payload; |
| 93 | ||
| 94 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 95 | 0 | if(err) return callback(err, false); |
| 96 | ||
| 97 | // MongoDB API Second Step | |
| 98 | 0 | MongoDBGSSAPISecondStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback); |
| 99 | }); | |
| 100 | }); | |
| 101 | } | |
| 102 | ||
| 103 | // | |
| 104 | // Perform first step against mongodb | |
| 105 | 1 | var MongoDBGSSAPISecondStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) { |
| 106 | // Build Authentication command to send to MongoDB | |
| 107 | 0 | var command = { |
| 108 | saslContinue: 1 | |
| 109 | , conversationId: doc.conversationId | |
| 110 | , payload: payload | |
| 111 | }; | |
| 112 | ||
| 113 | // Execute the command | |
| 114 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 115 | 0 | if(err) return callback(err, false); |
| 116 | ||
| 117 | // Get the result document | |
| 118 | 0 | doc = doc.documents[0]; |
| 119 | ||
| 120 | // Call next transition for kerberos | |
| 121 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 122 | 0 | if(err) return callback(err, false); |
| 123 | ||
| 124 | // Call the last and third step | |
| 125 | 0 | MongoDBGSSAPIThirdStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback); |
| 126 | }); | |
| 127 | }); | |
| 128 | } | |
| 129 | ||
| 130 | 1 | var MongoDBGSSAPIThirdStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) { |
| 131 | // Build final command | |
| 132 | 0 | var command = { |
| 133 | saslContinue: 1 | |
| 134 | , conversationId: doc.conversationId | |
| 135 | , payload: payload | |
| 136 | }; | |
| 137 | ||
| 138 | // Let's finish the auth process against mongodb | |
| 139 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 140 | 0 | if(err) return callback(err, false); |
| 141 | ||
| 142 | 0 | mongo_auth_process.transition(null, function(err, payload) { |
| 143 | 0 | if(err) return callback(err, false); |
| 144 | 0 | callback(null, true); |
| 145 | }); | |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , Binary = require('bson').Binary | |
| 4 | , format = require('util').format; | |
| 5 | ||
| 6 | 1 | var authenticate = function(db, username, password, options, callback) { |
| 7 | 0 | var numberOfConnections = 0; |
| 8 | 0 | var errorObject = null; |
| 9 | ||
| 10 | 0 | if(options['connection'] != null) { |
| 11 | //if a connection was explicitly passed on options, then we have only one... | |
| 12 | 0 | numberOfConnections = 1; |
| 13 | } else { | |
| 14 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 15 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 16 | 0 | options['onAll'] = true; |
| 17 | } | |
| 18 | ||
| 19 | // Create payload | |
| 20 | 0 | var payload = new Binary(format("\x00%s\x00%s", username, password)); |
| 21 | ||
| 22 | // Let's start the sasl process | |
| 23 | 0 | var command = { |
| 24 | saslStart: 1 | |
| 25 | , mechanism: 'PLAIN' | |
| 26 | , payload: payload | |
| 27 | , autoAuthorize: 1 | |
| 28 | }; | |
| 29 | ||
| 30 | // Grab all the connections | |
| 31 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 32 | ||
| 33 | // Authenticate all connections | |
| 34 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 35 | 0 | var connection = connections[i]; |
| 36 | // Execute first sasl step | |
| 37 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, result) { |
| 38 | // Count down | |
| 39 | 0 | numberOfConnections = numberOfConnections - 1; |
| 40 | ||
| 41 | // Ensure we save any error | |
| 42 | 0 | if(err) { |
| 43 | 0 | errorObject = err; |
| 44 | 0 | } else if(result.documents[0].err != null || result.documents[0].errmsg != null){ |
| 45 | 0 | errorObject = utils.toError(result.documents[0]); |
| 46 | } | |
| 47 | ||
| 48 | // Work around the case where the number of connections are 0 | |
| 49 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 50 | 0 | var internalCallback = callback; |
| 51 | 0 | callback = null; |
| 52 | ||
| 53 | 0 | if(errorObject == null && result.documents[0].ok == 1) { |
| 54 | // We authenticated correctly save the credentials | |
| 55 | 0 | db.serverConfig.auth.add('PLAIN', db.databaseName, username, password); |
| 56 | // Return callback | |
| 57 | 0 | internalCallback(errorObject, true); |
| 58 | } else { | |
| 59 | 0 | internalCallback(errorObject, false); |
| 60 | } | |
| 61 | } | |
| 62 | }); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , format = require('util').format; | |
| 4 | ||
| 5 | // Kerberos class | |
| 6 | 1 | var Kerberos = null; |
| 7 | 1 | var MongoAuthProcess = null; |
| 8 | // Try to grab the Kerberos class | |
| 9 | 1 | try { |
| 10 | 1 | Kerberos = require('kerberos').Kerberos |
| 11 | // Authentication process for Mongo | |
| 12 | 1 | MongoAuthProcess = require('kerberos').processes.MongoAuthProcess |
| 13 | } catch(err) {} | |
| 14 | ||
| 15 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 16 | 0 | var numberOfConnections = 0; |
| 17 | 0 | var errorObject = null; |
| 18 | // We don't have the Kerberos library | |
| 19 | 0 | if(Kerberos == null) return callback(new Error("Kerberos library is not installed")); |
| 20 | ||
| 21 | 0 | if(options['connection'] != null) { |
| 22 | //if a connection was explicitly passed on options, then we have only one... | |
| 23 | 0 | numberOfConnections = 1; |
| 24 | } else { | |
| 25 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 26 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 27 | 0 | options['onAll'] = true; |
| 28 | } | |
| 29 | ||
| 30 | // Set the sspi server name | |
| 31 | 0 | var gssapiServiceName = options['gssapiServiceName'] || 'mongodb'; |
| 32 | ||
| 33 | // Grab all the connections | |
| 34 | 0 | var connections = db.serverConfig.allRawConnections(); |
| 35 | 0 | var error = null; |
| 36 | ||
| 37 | // Authenticate all connections | |
| 38 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 39 | // Start Auth process for a connection | |
| 40 | 0 | SSIPAuthenticate(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) { |
| 41 | // Adjust number of connections left to connect | |
| 42 | 0 | numberOfConnections = numberOfConnections - 1; |
| 43 | // If we have an error save it | |
| 44 | 0 | if(err) error = err; |
| 45 | ||
| 46 | // We are done | |
| 47 | 0 | if(numberOfConnections == 0) { |
| 48 | 0 | if(err) return callback(err, false); |
| 49 | // We authenticated correctly save the credentials | |
| 50 | 0 | db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName); |
| 51 | // Return valid callback | |
| 52 | 0 | return callback(null, true); |
| 53 | } | |
| 54 | }); | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | 1 | var SSIPAuthenticate = function(db, username, password, authdb, service_name, connection, callback) { |
| 59 | // -------------------------------------------------------------- | |
| 60 | // Async Version | |
| 61 | // -------------------------------------------------------------- | |
| 62 | 0 | var command = { |
| 63 | saslStart: 1 | |
| 64 | , mechanism: 'GSSAPI' | |
| 65 | , payload: '' | |
| 66 | , autoAuthorize: 1 | |
| 67 | }; | |
| 68 | ||
| 69 | // Create authenticator | |
| 70 | 0 | var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, service_name); |
| 71 | ||
| 72 | // Execute first sasl step | |
| 73 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 74 | 0 | if(err) return callback(err); |
| 75 | 0 | doc = doc.documents[0]; |
| 76 | ||
| 77 | 0 | mongo_auth_process.init(username, password, function(err) { |
| 78 | 0 | if(err) return callback(err); |
| 79 | ||
| 80 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 81 | 0 | if(err) return callback(err); |
| 82 | ||
| 83 | // Perform the next step against mongod | |
| 84 | 0 | var command = { |
| 85 | saslContinue: 1 | |
| 86 | , conversationId: doc.conversationId | |
| 87 | , payload: payload | |
| 88 | }; | |
| 89 | ||
| 90 | // Execute the command | |
| 91 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 92 | 0 | if(err) return callback(err); |
| 93 | 0 | doc = doc.documents[0]; |
| 94 | ||
| 95 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 96 | 0 | if(err) return callback(err); |
| 97 | ||
| 98 | // Perform the next step against mongod | |
| 99 | 0 | var command = { |
| 100 | saslContinue: 1 | |
| 101 | , conversationId: doc.conversationId | |
| 102 | , payload: payload | |
| 103 | }; | |
| 104 | ||
| 105 | // Execute the command | |
| 106 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 107 | 0 | if(err) return callback(err); |
| 108 | 0 | doc = doc.documents[0]; |
| 109 | ||
| 110 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 111 | // Perform the next step against mongod | |
| 112 | 0 | var command = { |
| 113 | saslContinue: 1 | |
| 114 | , conversationId: doc.conversationId | |
| 115 | , payload: payload | |
| 116 | }; | |
| 117 | ||
| 118 | // Execute the command | |
| 119 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 120 | 0 | if(err) return callback(err); |
| 121 | 0 | doc = doc.documents[0]; |
| 122 | ||
| 123 | 0 | if(doc.done) return callback(null, true); |
| 124 | 0 | callback(new Error("Authentication failed"), false); |
| 125 | }); | |
| 126 | }); | |
| 127 | }); | |
| 128 | }); | |
| 129 | }); | |
| 130 | }); | |
| 131 | }); | |
| 132 | }); | |
| 133 | } | |
| 134 | ||
| 135 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , Binary = require('bson').Binary | |
| 4 | , format = require('util').format; | |
| 5 | ||
| 6 | 1 | var authenticate = function(db, username, password, options, callback) { |
| 7 | 0 | var numberOfConnections = 0; |
| 8 | 0 | var errorObject = null; |
| 9 | ||
| 10 | 0 | if(options['connection'] != null) { |
| 11 | //if a connection was explicitly passed on options, then we have only one... | |
| 12 | 0 | numberOfConnections = 1; |
| 13 | } else { | |
| 14 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 15 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 16 | 0 | options['onAll'] = true; |
| 17 | } | |
| 18 | ||
| 19 | // Let's start the sasl process | |
| 20 | 0 | var command = { |
| 21 | authenticate: 1 | |
| 22 | , mechanism: 'MONGODB-X509' | |
| 23 | , user: username | |
| 24 | }; | |
| 25 | ||
| 26 | // Grab all the connections | |
| 27 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 28 | ||
| 29 | // Authenticate all connections | |
| 30 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 31 | 0 | var connection = connections[i]; |
| 32 | // Execute first sasl step | |
| 33 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, result) { |
| 34 | // Count down | |
| 35 | 0 | numberOfConnections = numberOfConnections - 1; |
| 36 | ||
| 37 | // Ensure we save any error | |
| 38 | 0 | if(err) { |
| 39 | 0 | errorObject = err; |
| 40 | 0 | } else if(result.documents[0].err != null || result.documents[0].errmsg != null){ |
| 41 | 0 | errorObject = utils.toError(result.documents[0]); |
| 42 | } | |
| 43 | ||
| 44 | // Work around the case where the number of connections are 0 | |
| 45 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 46 | 0 | var internalCallback = callback; |
| 47 | 0 | callback = null; |
| 48 | ||
| 49 | 0 | if(errorObject == null && result.documents[0].ok == 1) { |
| 50 | // We authenticated correctly save the credentials | |
| 51 | 0 | db.serverConfig.auth.add('MONGODB-X509', db.databaseName, username, password); |
| 52 | // Return callback | |
| 53 | 0 | internalCallback(errorObject, true); |
| 54 | } else { | |
| 55 | 0 | internalCallback(errorObject, false); |
| 56 | } | |
| 57 | } | |
| 58 | }); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | * @ignore | |
| 4 | */ | |
| 5 | 1 | var InsertCommand = require('./commands/insert_command').InsertCommand |
| 6 | , QueryCommand = require('./commands/query_command').QueryCommand | |
| 7 | , DeleteCommand = require('./commands/delete_command').DeleteCommand | |
| 8 | , UpdateCommand = require('./commands/update_command').UpdateCommand | |
| 9 | , DbCommand = require('./commands/db_command').DbCommand | |
| 10 | , ObjectID = require('bson').ObjectID | |
| 11 | , Code = require('bson').Code | |
| 12 | , Cursor = require('./cursor').Cursor | |
| 13 | , utils = require('./utils') | |
| 14 | , shared = require('./collection/shared') | |
| 15 | , core = require('./collection/core') | |
| 16 | , query = require('./collection/query') | |
| 17 | , index = require('./collection/index') | |
| 18 | , geo = require('./collection/geo') | |
| 19 | , commands = require('./collection/commands') | |
| 20 | , aggregation = require('./collection/aggregation'); | |
| 21 | ||
| 22 | /** | |
| 23 | * Create a new Collection instance (INTERNAL TYPE, do not instantiate directly) | |
| 24 | * | |
| 25 | * Options | |
| 26 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 27 | * - **slaveOk** {Boolean, default:false}, Allow reads from secondaries. | |
| 28 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 29 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 30 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 31 | * | |
| 32 | * @class Represents a Collection | |
| 33 | * @param {Object} db db instance. | |
| 34 | * @param {String} collectionName collection name. | |
| 35 | * @param {Object} [pkFactory] alternative primary key factory. | |
| 36 | * @param {Object} [options] additional options for the collection. | |
| 37 | * @return {Object} a collection instance. | |
| 38 | */ | |
| 39 | 1 | function Collection (db, collectionName, pkFactory, options) { |
| 40 | 0 | if(!(this instanceof Collection)) return new Collection(db, collectionName, pkFactory, options); |
| 41 | ||
| 42 | 0 | shared.checkCollectionName(collectionName); |
| 43 | ||
| 44 | 0 | this.db = db; |
| 45 | 0 | this.collectionName = collectionName; |
| 46 | 0 | this.internalHint = null; |
| 47 | 0 | this.opts = options != null && ('object' === typeof options) ? options : {}; |
| 48 | 0 | this.slaveOk = options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk; |
| 49 | 0 | this.serializeFunctions = options == null || options.serializeFunctions == null ? db.serializeFunctions : options.serializeFunctions; |
| 50 | 0 | this.raw = options == null || options.raw == null ? db.raw : options.raw; |
| 51 | ||
| 52 | 0 | this.readPreference = options == null || options.readPreference == null ? db.serverConfig.options.readPreference : options.readPreference; |
| 53 | 0 | this.readPreference = this.readPreference == null ? 'primary' : this.readPreference; |
| 54 | ||
| 55 | ||
| 56 | 0 | this.pkFactory = pkFactory == null |
| 57 | ? ObjectID | |
| 58 | : pkFactory; | |
| 59 | ||
| 60 | // Server Capabilities | |
| 61 | 0 | this.serverCapabilities = this.db.serverConfig._serverCapabilities; |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Inserts a single document or a an array of documents into MongoDB. | |
| 66 | * | |
| 67 | * Options | |
| 68 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 69 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 70 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 71 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 72 | * - **continueOnError/keepGoing** {Boolean, default:false}, keep inserting documents even if one document has an error, *mongodb 1.9.1 >*. | |
| 73 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 74 | * - **forceServerObjectId** {Boolean, default:false}, let server assign ObjectId instead of the driver | |
| 75 | * - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS) | |
| 76 | * | |
| 77 | * Deprecated Options | |
| 78 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 79 | * | |
| 80 | * @param {Array|Object} docs | |
| 81 | * @param {Object} [options] optional options for insert command | |
| 82 | * @param {Function} [callback] optional callback for the function, must be provided when using a writeconcern | |
| 83 | * @return {null} | |
| 84 | * @api public | |
| 85 | */ | |
| 86 | 2 | Collection.prototype.insert = function() { return core.insert; }(); |
| 87 | ||
| 88 | /** | |
| 89 | * Removes documents specified by `selector` from the db. | |
| 90 | * | |
| 91 | * Options | |
| 92 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 93 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 94 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 95 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 96 | * - **single** {Boolean, default:false}, removes the first document found. | |
| 97 | * | |
| 98 | * Deprecated Options | |
| 99 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 100 | * | |
| 101 | * @param {Object} [selector] optional select, no selector is equivalent to removing all documents. | |
| 102 | * @param {Object} [options] additional options during remove. | |
| 103 | * @param {Function} [callback] must be provided if you performing a remove with a writeconcern | |
| 104 | * @return {null} | |
| 105 | * @api public | |
| 106 | */ | |
| 107 | 2 | Collection.prototype.remove = function() { return core.remove; }(); |
| 108 | ||
| 109 | /** | |
| 110 | * Renames the collection. | |
| 111 | * | |
| 112 | * Options | |
| 113 | * - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists. | |
| 114 | * | |
| 115 | * @param {String} newName the new name of the collection. | |
| 116 | * @param {Object} [options] returns option results. | |
| 117 | * @param {Function} callback the callback accepting the result | |
| 118 | * @return {null} | |
| 119 | * @api public | |
| 120 | */ | |
| 121 | 2 | Collection.prototype.rename = function() { return commands.rename; }(); |
| 122 | ||
| 123 | /** | |
| 124 | * Save a document. Simple full document replacement function. Not recommended for efficiency, use atomic | |
| 125 | * operators and update instead for more efficient operations. | |
| 126 | * | |
| 127 | * Options | |
| 128 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 129 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 130 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 131 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 132 | * | |
| 133 | * Deprecated Options | |
| 134 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 135 | * | |
| 136 | * @param {Object} [doc] the document to save | |
| 137 | * @param {Object} [options] additional options during remove. | |
| 138 | * @param {Function} [callback] must be provided if you performing a safe save | |
| 139 | * @return {null} | |
| 140 | * @api public | |
| 141 | */ | |
| 142 | 2 | Collection.prototype.save = function() { return core.save; }(); |
| 143 | ||
| 144 | /** | |
| 145 | * Updates documents. | |
| 146 | * | |
| 147 | * Options | |
| 148 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 149 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 150 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 151 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 152 | * - **upsert** {Boolean, default:false}, perform an upsert operation. | |
| 153 | * - **multi** {Boolean, default:false}, update all documents matching the selector. | |
| 154 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 155 | * - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS) | |
| 156 | * | |
| 157 | * Deprecated Options | |
| 158 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 159 | * | |
| 160 | * @param {Object} selector the query to select the document/documents to be updated | |
| 161 | * @param {Object} document the fields/vals to be updated, or in the case of an upsert operation, inserted. | |
| 162 | * @param {Object} [options] additional options during update. | |
| 163 | * @param {Function} [callback] must be provided if you performing an update with a writeconcern | |
| 164 | * @return {null} | |
| 165 | * @api public | |
| 166 | */ | |
| 167 | 2 | Collection.prototype.update = function() { return core.update; }(); |
| 168 | ||
| 169 | /** | |
| 170 | * The distinct command returns returns a list of distinct values for the given key across a collection. | |
| 171 | * | |
| 172 | * Options | |
| 173 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 174 | * | |
| 175 | * @param {String} key key to run distinct against. | |
| 176 | * @param {Object} [query] option query to narrow the returned objects. | |
| 177 | * @param {Object} [options] additional options during update. | |
| 178 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from distinct or null if an error occured. | |
| 179 | * @return {null} | |
| 180 | * @api public | |
| 181 | */ | |
| 182 | 2 | Collection.prototype.distinct = function() { return commands.distinct; }(); |
| 183 | ||
| 184 | /** | |
| 185 | * Count number of matching documents in the db to a query. | |
| 186 | * | |
| 187 | * Options | |
| 188 | * - **skip** {Number}, The number of documents to skip for the count. | |
| 189 | * - **limit** {Number}, The limit of documents to count. | |
| 190 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 191 | * | |
| 192 | * @param {Object} [query] query to filter by before performing count. | |
| 193 | * @param {Object} [options] additional options during count. | |
| 194 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the count method or null if an error occured. | |
| 195 | * @return {null} | |
| 196 | * @api public | |
| 197 | */ | |
| 198 | 2 | Collection.prototype.count = function() { return commands.count; }(); |
| 199 | ||
| 200 | /** | |
| 201 | * Drop the collection | |
| 202 | * | |
| 203 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the drop method or null if an error occured. | |
| 204 | * @return {null} | |
| 205 | * @api public | |
| 206 | */ | |
| 207 | 1 | Collection.prototype.drop = function drop(callback) { |
| 208 | 0 | this.db.dropCollection(this.collectionName, callback); |
| 209 | }; | |
| 210 | ||
| 211 | /** | |
| 212 | * Find and update a document. | |
| 213 | * | |
| 214 | * Options | |
| 215 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 216 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 217 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 218 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 219 | * - **remove** {Boolean, default:false}, set to true to remove the object before returning. | |
| 220 | * - **upsert** {Boolean, default:false}, perform an upsert operation. | |
| 221 | * - **new** {Boolean, default:false}, set to true if you want to return the modified object rather than the original. Ignored for remove. | |
| 222 | * | |
| 223 | * Deprecated Options | |
| 224 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 225 | * | |
| 226 | * @param {Object} query query object to locate the object to modify | |
| 227 | * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate | |
| 228 | * @param {Object} doc - the fields/vals to be updated | |
| 229 | * @param {Object} [options] additional options during update. | |
| 230 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndModify method or null if an error occured. | |
| 231 | * @return {null} | |
| 232 | * @api public | |
| 233 | */ | |
| 234 | 2 | Collection.prototype.findAndModify = function() { return core.findAndModify; }(); |
| 235 | ||
| 236 | /** | |
| 237 | * Find and remove a document | |
| 238 | * | |
| 239 | * Options | |
| 240 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 241 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 242 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 243 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 244 | * | |
| 245 | * Deprecated Options | |
| 246 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 247 | * | |
| 248 | * @param {Object} query query object to locate the object to modify | |
| 249 | * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate | |
| 250 | * @param {Object} [options] additional options during update. | |
| 251 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndRemove method or null if an error occured. | |
| 252 | * @return {null} | |
| 253 | * @api public | |
| 254 | */ | |
| 255 | 2 | Collection.prototype.findAndRemove = function() { return core.findAndRemove; }(); |
| 256 | ||
| 257 | /** | |
| 258 | * Creates a cursor for a query that can be used to iterate over results from MongoDB | |
| 259 | * | |
| 260 | * Various argument possibilities | |
| 261 | * - callback? | |
| 262 | * - selector, callback?, | |
| 263 | * - selector, fields, callback? | |
| 264 | * - selector, options, callback? | |
| 265 | * - selector, fields, options, callback? | |
| 266 | * - selector, fields, skip, limit, callback? | |
| 267 | * - selector, fields, skip, limit, timeout, callback? | |
| 268 | * | |
| 269 | * Options | |
| 270 | * - **limit** {Number, default:0}, sets the limit of documents returned in the query. | |
| 271 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 272 | * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} | |
| 273 | * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). | |
| 274 | * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} | |
| 275 | * - **explain** {Boolean, default:false}, explain the query instead of returning the data. | |
| 276 | * - **snapshot** {Boolean, default:false}, snapshot query. | |
| 277 | * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. | |
| 278 | * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. | |
| 279 | * - **tailableRetryInterval** {Number, default:100}, specify the miliseconds between getMores on tailable cursor. | |
| 280 | * - **numberOfRetries** {Number, default:5}, specify the number of times to retry the tailable cursor. | |
| 281 | * - **awaitdata** {Boolean, default:false} allow the cursor to wait for data, only applicable for tailable cursor. | |
| 282 | * - **oplogReplay** {Boolean, default:false} sets an internal flag, only applicable for tailable cursor. | |
| 283 | * - **exhaust** {Boolean, default:false} have the server send all the documents at once as getMore packets, not recommended. | |
| 284 | * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. | |
| 285 | * - **returnKey** {Boolean, default:false}, only return the index key. | |
| 286 | * - **maxScan** {Number}, Limit the number of items to scan. | |
| 287 | * - **min** {Number}, Set index bounds. | |
| 288 | * - **max** {Number}, Set index bounds. | |
| 289 | * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. | |
| 290 | * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 291 | * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. | |
| 292 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 293 | * - **numberOfRetries** {Number, default:5}, if using awaidata specifies the number of times to retry on timeout. | |
| 294 | * - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system | |
| 295 | * | |
| 296 | * @param {Object|ObjectID} query query object to locate the object to modify | |
| 297 | * @param {Object} [options] additional options during update. | |
| 298 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the find method or null if an error occured. | |
| 299 | * @return {Cursor} returns a cursor to the query | |
| 300 | * @api public | |
| 301 | */ | |
| 302 | 2 | Collection.prototype.find = function() { return query.find; }(); |
| 303 | ||
| 304 | /** | |
| 305 | * Finds a single document based on the query | |
| 306 | * | |
| 307 | * Various argument possibilities | |
| 308 | * - callback? | |
| 309 | * - selector, callback?, | |
| 310 | * - selector, fields, callback? | |
| 311 | * - selector, options, callback? | |
| 312 | * - selector, fields, options, callback? | |
| 313 | * - selector, fields, skip, limit, callback? | |
| 314 | * - selector, fields, skip, limit, timeout, callback? | |
| 315 | * | |
| 316 | * Options | |
| 317 | * - **limit** {Number, default:0}, sets the limit of documents returned in the query. | |
| 318 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 319 | * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} | |
| 320 | * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). | |
| 321 | * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} | |
| 322 | * - **explain** {Boolean, default:false}, explain the query instead of returning the data. | |
| 323 | * - **snapshot** {Boolean, default:false}, snapshot query. | |
| 324 | * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. | |
| 325 | * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. | |
| 326 | * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. | |
| 327 | * - **returnKey** {Boolean, default:false}, only return the index key. | |
| 328 | * - **maxScan** {Number}, Limit the number of items to scan. | |
| 329 | * - **min** {Number}, Set index bounds. | |
| 330 | * - **max** {Number}, Set index bounds. | |
| 331 | * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. | |
| 332 | * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 333 | * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. | |
| 334 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 335 | * - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system | |
| 336 | * | |
| 337 | * @param {Object|ObjectID} query query object to locate the object to modify | |
| 338 | * @param {Object} [options] additional options during update. | |
| 339 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findOne method or null if an error occured. | |
| 340 | * @return {Cursor} returns a cursor to the query | |
| 341 | * @api public | |
| 342 | */ | |
| 343 | 2 | Collection.prototype.findOne = function() { return query.findOne; }(); |
| 344 | ||
| 345 | /** | |
| 346 | * Creates an index on the collection. | |
| 347 | * | |
| 348 | * Options | |
| 349 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 350 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 351 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 352 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 353 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 354 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 355 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 356 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 357 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 358 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 359 | * - **v** {Number}, specify the format version of the indexes. | |
| 360 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 361 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 362 | * | |
| 363 | * Deprecated Options | |
| 364 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 365 | * | |
| 366 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 367 | * @param {Object} [options] additional options during update. | |
| 368 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the createIndex method or null if an error occured. | |
| 369 | * @return {null} | |
| 370 | * @api public | |
| 371 | */ | |
| 372 | 2 | Collection.prototype.createIndex = function() { return index.createIndex; }(); |
| 373 | ||
| 374 | /** | |
| 375 | * Ensures that an index exists, if it does not it creates it | |
| 376 | * | |
| 377 | * Options | |
| 378 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 379 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 380 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 381 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 382 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 383 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 384 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 385 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 386 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 387 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 388 | * - **v** {Number}, specify the format version of the indexes. | |
| 389 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 390 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 391 | * | |
| 392 | * Deprecated Options | |
| 393 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 394 | * | |
| 395 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 396 | * @param {Object} [options] additional options during update. | |
| 397 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the ensureIndex method or null if an error occured. | |
| 398 | * @return {null} | |
| 399 | * @api public | |
| 400 | */ | |
| 401 | 2 | Collection.prototype.ensureIndex = function() { return index.ensureIndex; }(); |
| 402 | ||
| 403 | /** | |
| 404 | * Retrieves this collections index info. | |
| 405 | * | |
| 406 | * Options | |
| 407 | * - **full** {Boolean, default:false}, returns the full raw index information. | |
| 408 | * | |
| 409 | * @param {Object} [options] additional options during update. | |
| 410 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexInformation method or null if an error occured. | |
| 411 | * @return {null} | |
| 412 | * @api public | |
| 413 | */ | |
| 414 | 2 | Collection.prototype.indexInformation = function() { return index.indexInformation; }(); |
| 415 | ||
| 416 | /** | |
| 417 | * Drops an index from this collection. | |
| 418 | * | |
| 419 | * @param {String} name | |
| 420 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndex method or null if an error occured. | |
| 421 | * @return {null} | |
| 422 | * @api public | |
| 423 | */ | |
| 424 | 1 | Collection.prototype.dropIndex = function dropIndex (name, callback) { |
| 425 | 0 | this.db.dropIndex(this.collectionName, name, callback); |
| 426 | }; | |
| 427 | ||
| 428 | /** | |
| 429 | * Drops all indexes from this collection. | |
| 430 | * | |
| 431 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropAllIndexes method or null if an error occured. | |
| 432 | * @return {null} | |
| 433 | * @api public | |
| 434 | */ | |
| 435 | 2 | Collection.prototype.dropAllIndexes = function() { return index.dropAllIndexes; }(); |
| 436 | ||
| 437 | /** | |
| 438 | * Drops all indexes from this collection. | |
| 439 | * | |
| 440 | * @deprecated | |
| 441 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndexes method or null if an error occured. | |
| 442 | * @return {null} | |
| 443 | * @api private | |
| 444 | */ | |
| 445 | 2 | Collection.prototype.dropIndexes = function() { return Collection.prototype.dropAllIndexes; }(); |
| 446 | ||
| 447 | /** | |
| 448 | * Reindex all indexes on the collection | |
| 449 | * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. | |
| 450 | * | |
| 451 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the reIndex method or null if an error occured. | |
| 452 | * @return {null} | |
| 453 | * @api public | |
| 454 | **/ | |
| 455 | 1 | Collection.prototype.reIndex = function(callback) { |
| 456 | 0 | this.db.reIndex(this.collectionName, callback); |
| 457 | } | |
| 458 | ||
| 459 | /** | |
| 460 | * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. | |
| 461 | * | |
| 462 | * Options | |
| 463 | * - **out** {Object}, sets the output target for the map reduce job. *{inline:1} | {replace:'collectionName'} | {merge:'collectionName'} | {reduce:'collectionName'}* | |
| 464 | * - **query** {Object}, query filter object. | |
| 465 | * - **sort** {Object}, sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces. | |
| 466 | * - **limit** {Number}, number of objects to return from collection. | |
| 467 | * - **keeptemp** {Boolean, default:false}, keep temporary data. | |
| 468 | * - **finalize** {Function | String}, finalize function. | |
| 469 | * - **scope** {Object}, can pass in variables that can be access from map/reduce/finalize. | |
| 470 | * - **jsMode** {Boolean, default:false}, it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X. | |
| 471 | * - **verbose** {Boolean, default:false}, provide statistics on job execution time. | |
| 472 | * - **readPreference** {String, only for inline results}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 473 | * | |
| 474 | * @param {Function|String} map the mapping function. | |
| 475 | * @param {Function|String} reduce the reduce function. | |
| 476 | * @param {Objects} [options] options for the map reduce job. | |
| 477 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the mapReduce method or null if an error occured. | |
| 478 | * @return {null} | |
| 479 | * @api public | |
| 480 | */ | |
| 481 | 2 | Collection.prototype.mapReduce = function() { return aggregation.mapReduce; }(); |
| 482 | ||
| 483 | /** | |
| 484 | * Run a group command across a collection | |
| 485 | * | |
| 486 | * Options | |
| 487 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 488 | * | |
| 489 | * @param {Object|Array|Function|Code} keys an object, array or function expressing the keys to group by. | |
| 490 | * @param {Object} condition an optional condition that must be true for a row to be considered. | |
| 491 | * @param {Object} initial initial value of the aggregation counter object. | |
| 492 | * @param {Function|Code} reduce the reduce function aggregates (reduces) the objects iterated | |
| 493 | * @param {Function|Code} finalize an optional function to be run on each item in the result set just before the item is returned. | |
| 494 | * @param {Boolean} command specify if you wish to run using the internal group command or using eval, default is true. | |
| 495 | * @param {Object} [options] additional options during update. | |
| 496 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the group method or null if an error occured. | |
| 497 | * @return {null} | |
| 498 | * @api public | |
| 499 | */ | |
| 500 | 2 | Collection.prototype.group = function() { return aggregation.group; }(); |
| 501 | ||
| 502 | /** | |
| 503 | * Returns the options of the collection. | |
| 504 | * | |
| 505 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the options method or null if an error occured. | |
| 506 | * @return {null} | |
| 507 | * @api public | |
| 508 | */ | |
| 509 | 2 | Collection.prototype.options = function() { return commands.options; }(); |
| 510 | ||
| 511 | /** | |
| 512 | * Returns if the collection is a capped collection | |
| 513 | * | |
| 514 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the isCapped method or null if an error occured. | |
| 515 | * @return {null} | |
| 516 | * @api public | |
| 517 | */ | |
| 518 | 2 | Collection.prototype.isCapped = function() { return commands.isCapped; }(); |
| 519 | ||
| 520 | /** | |
| 521 | * Checks if one or more indexes exist on the collection | |
| 522 | * | |
| 523 | * @param {String|Array} indexNames check if one or more indexes exist on the collection. | |
| 524 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexExists method or null if an error occured. | |
| 525 | * @return {null} | |
| 526 | * @api public | |
| 527 | */ | |
| 528 | 2 | Collection.prototype.indexExists = function() { return index.indexExists; }(); |
| 529 | ||
| 530 | /** | |
| 531 | * Execute the geoNear command to search for items in the collection | |
| 532 | * | |
| 533 | * Options | |
| 534 | * - **num** {Number}, max number of results to return. | |
| 535 | * - **maxDistance** {Number}, include results up to maxDistance from the point. | |
| 536 | * - **distanceMultiplier** {Number}, include a value to multiply the distances with allowing for range conversions. | |
| 537 | * - **query** {Object}, filter the results by a query. | |
| 538 | * - **spherical** {Boolean, default:false}, perform query using a spherical model. | |
| 539 | * - **uniqueDocs** {Boolean, default:false}, the closest location in a document to the center of the search region will always be returned MongoDB > 2.X. | |
| 540 | * - **includeLocs** {Boolean, default:false}, include the location data fields in the top level of the results MongoDB > 2.X. | |
| 541 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 542 | * | |
| 543 | * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. | |
| 544 | * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. | |
| 545 | * @param {Objects} [options] options for the map reduce job. | |
| 546 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoNear method or null if an error occured. | |
| 547 | * @return {null} | |
| 548 | * @api public | |
| 549 | */ | |
| 550 | 2 | Collection.prototype.geoNear = function() { return geo.geoNear; }(); |
| 551 | ||
| 552 | /** | |
| 553 | * Execute a geo search using a geo haystack index on a collection. | |
| 554 | * | |
| 555 | * Options | |
| 556 | * - **maxDistance** {Number}, include results up to maxDistance from the point. | |
| 557 | * - **search** {Object}, filter the results by a query. | |
| 558 | * - **limit** {Number}, max number of results to return. | |
| 559 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 560 | * | |
| 561 | * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. | |
| 562 | * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. | |
| 563 | * @param {Objects} [options] options for the map reduce job. | |
| 564 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoHaystackSearch method or null if an error occured. | |
| 565 | * @return {null} | |
| 566 | * @api public | |
| 567 | */ | |
| 568 | 2 | Collection.prototype.geoHaystackSearch = function() { return geo.geoHaystackSearch; }(); |
| 569 | ||
| 570 | /** | |
| 571 | * Retrieve all the indexes on the collection. | |
| 572 | * | |
| 573 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexes method or null if an error occured. | |
| 574 | * @return {null} | |
| 575 | * @api public | |
| 576 | */ | |
| 577 | 1 | Collection.prototype.indexes = function indexes(callback) { |
| 578 | 0 | this.db.indexInformation(this.collectionName, {full:true}, callback); |
| 579 | } | |
| 580 | ||
| 581 | /** | |
| 582 | * Execute an aggregation framework pipeline against the collection, needs MongoDB >= 2.2 | |
| 583 | * | |
| 584 | * Options | |
| 585 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 586 | * | |
| 587 | * @param {Array} array containing all the aggregation framework commands for the execution. | |
| 588 | * @param {Object} [options] additional options during update. | |
| 589 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the aggregate method or null if an error occured. | |
| 590 | * @return {null} | |
| 591 | * @api public | |
| 592 | */ | |
| 593 | 2 | Collection.prototype.aggregate = function() { return aggregation.aggregate; }(); |
| 594 | ||
| 595 | /** | |
| 596 | * Get all the collection statistics. | |
| 597 | * | |
| 598 | * Options | |
| 599 | * - **scale** {Number}, divide the returned sizes by scale value. | |
| 600 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 601 | * | |
| 602 | * @param {Objects} [options] options for the stats command. | |
| 603 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the stats method or null if an error occured. | |
| 604 | * @return {null} | |
| 605 | * @api public | |
| 606 | */ | |
| 607 | 2 | Collection.prototype.stats = function() { return commands.stats; }(); |
| 608 | ||
| 609 | /** | |
| 610 | * @ignore | |
| 611 | */ | |
| 612 | 1 | Object.defineProperty(Collection.prototype, "hint", { |
| 613 | enumerable: true | |
| 614 | , get: function () { | |
| 615 | 0 | return this.internalHint; |
| 616 | } | |
| 617 | , set: function (v) { | |
| 618 | 0 | this.internalHint = shared.normalizeHintField(v); |
| 619 | } | |
| 620 | }); | |
| 621 | ||
| 622 | /** | |
| 623 | * Expose. | |
| 624 | */ | |
| 625 | 1 | exports.Collection = Collection; |
| 626 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils') | |
| 3 | , AggregationCursor = require('../aggregation_cursor').AggregationCursor | |
| 4 | , Code = require('bson').Code | |
| 5 | , DbCommand = require('../commands/db_command').DbCommand; | |
| 6 | ||
| 7 | /** | |
| 8 | * Functions that are passed as scope args must | |
| 9 | * be converted to Code instances. | |
| 10 | * @ignore | |
| 11 | */ | |
| 12 | 1 | function processScope (scope) { |
| 13 | 0 | if (!utils.isObject(scope)) { |
| 14 | 0 | return scope; |
| 15 | } | |
| 16 | ||
| 17 | 0 | var keys = Object.keys(scope); |
| 18 | 0 | var i = keys.length; |
| 19 | 0 | var key; |
| 20 | ||
| 21 | 0 | while (i--) { |
| 22 | 0 | key = keys[i]; |
| 23 | 0 | if ('function' == typeof scope[key]) { |
| 24 | 0 | scope[key] = new Code(String(scope[key])); |
| 25 | } else { | |
| 26 | 0 | scope[key] = processScope(scope[key]); |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | 0 | return scope; |
| 31 | } | |
| 32 | ||
| 33 | 1 | var pipe = function() { |
| 34 | 0 | return new AggregationCursor(this, this.serverCapabilities); |
| 35 | } | |
| 36 | ||
| 37 | 1 | var mapReduce = function mapReduce (map, reduce, options, callback) { |
| 38 | 0 | if ('function' === typeof options) callback = options, options = {}; |
| 39 | // Out must allways be defined (make sure we don't break weirdly on pre 1.8+ servers) | |
| 40 | 0 | if(null == options.out) { |
| 41 | 0 | throw new Error("the out option parameter must be defined, see mongodb docs for possible values"); |
| 42 | } | |
| 43 | ||
| 44 | 0 | if ('function' === typeof map) { |
| 45 | 0 | map = map.toString(); |
| 46 | } | |
| 47 | ||
| 48 | 0 | if ('function' === typeof reduce) { |
| 49 | 0 | reduce = reduce.toString(); |
| 50 | } | |
| 51 | ||
| 52 | 0 | if ('function' === typeof options.finalize) { |
| 53 | 0 | options.finalize = options.finalize.toString(); |
| 54 | } | |
| 55 | ||
| 56 | 0 | var mapCommandHash = { |
| 57 | mapreduce: this.collectionName | |
| 58 | , map: map | |
| 59 | , reduce: reduce | |
| 60 | }; | |
| 61 | ||
| 62 | // Add any other options passed in | |
| 63 | 0 | for (var name in options) { |
| 64 | 0 | if ('scope' == name) { |
| 65 | 0 | mapCommandHash[name] = processScope(options[name]); |
| 66 | } else { | |
| 67 | 0 | mapCommandHash[name] = options[name]; |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | // Set read preference if we set one | |
| 72 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 73 | ||
| 74 | // If we have a read preference and inline is not set as output fail hard | |
| 75 | 0 | if((readPreference != false && readPreference != 'primary') |
| 76 | && options['out'] && (options['out'].inline != 1 && options['out'] != 'inline')) { | |
| 77 | 0 | readPreference = 'primary'; |
| 78 | } | |
| 79 | ||
| 80 | // self | |
| 81 | 0 | var self = this; |
| 82 | 0 | var cmd = DbCommand.createDbCommand(this.db, mapCommandHash); |
| 83 | ||
| 84 | 0 | this.db._executeQueryCommand(cmd, {read:readPreference}, function (err, result) { |
| 85 | 0 | if(err) return callback(err); |
| 86 | 0 | if(!result || !result.documents || result.documents.length == 0) |
| 87 | 0 | return callback(Error("command failed to return results"), null) |
| 88 | ||
| 89 | // Check if we have an error | |
| 90 | 0 | if(1 != result.documents[0].ok || result.documents[0].err || result.documents[0].errmsg) { |
| 91 | 0 | return callback(utils.toError(result.documents[0])); |
| 92 | } | |
| 93 | ||
| 94 | // Create statistics value | |
| 95 | 0 | var stats = {}; |
| 96 | 0 | if(result.documents[0].timeMillis) stats['processtime'] = result.documents[0].timeMillis; |
| 97 | 0 | if(result.documents[0].counts) stats['counts'] = result.documents[0].counts; |
| 98 | 0 | if(result.documents[0].timing) stats['timing'] = result.documents[0].timing; |
| 99 | ||
| 100 | // invoked with inline? | |
| 101 | 0 | if(result.documents[0].results) { |
| 102 | // If we wish for no verbosity | |
| 103 | 0 | if(options['verbose'] == null || !options['verbose']) { |
| 104 | 0 | return callback(null, result.documents[0].results); |
| 105 | } | |
| 106 | 0 | return callback(null, result.documents[0].results, stats); |
| 107 | } | |
| 108 | ||
| 109 | // The returned collection | |
| 110 | 0 | var collection = null; |
| 111 | ||
| 112 | // If we have an object it's a different db | |
| 113 | 0 | if(result.documents[0].result != null && typeof result.documents[0].result == 'object') { |
| 114 | 0 | var doc = result.documents[0].result; |
| 115 | 0 | collection = self.db.db(doc.db).collection(doc.collection); |
| 116 | } else { | |
| 117 | // Create a collection object that wraps the result collection | |
| 118 | 0 | collection = self.db.collection(result.documents[0].result) |
| 119 | } | |
| 120 | ||
| 121 | // If we wish for no verbosity | |
| 122 | 0 | if(options['verbose'] == null || !options['verbose']) { |
| 123 | 0 | return callback(err, collection); |
| 124 | } | |
| 125 | ||
| 126 | // Return stats as third set of values | |
| 127 | 0 | callback(err, collection, stats); |
| 128 | }); | |
| 129 | }; | |
| 130 | ||
| 131 | /** | |
| 132 | * Group function helper | |
| 133 | * @ignore | |
| 134 | */ | |
| 135 | 1 | var groupFunction = function () { |
| 136 | 0 | var c = db[ns].find(condition); |
| 137 | 0 | var map = new Map(); |
| 138 | 0 | var reduce_function = reduce; |
| 139 | ||
| 140 | 0 | while (c.hasNext()) { |
| 141 | 0 | var obj = c.next(); |
| 142 | 0 | var key = {}; |
| 143 | ||
| 144 | 0 | for (var i = 0, len = keys.length; i < len; ++i) { |
| 145 | 0 | var k = keys[i]; |
| 146 | 0 | key[k] = obj[k]; |
| 147 | } | |
| 148 | ||
| 149 | 0 | var aggObj = map.get(key); |
| 150 | ||
| 151 | 0 | if (aggObj == null) { |
| 152 | 0 | var newObj = Object.extend({}, key); |
| 153 | 0 | aggObj = Object.extend(newObj, initial); |
| 154 | 0 | map.put(key, aggObj); |
| 155 | } | |
| 156 | ||
| 157 | 0 | reduce_function(obj, aggObj); |
| 158 | } | |
| 159 | ||
| 160 | 0 | return { "result": map.values() }; |
| 161 | }.toString(); | |
| 162 | ||
| 163 | 1 | var group = function group(keys, condition, initial, reduce, finalize, command, options, callback) { |
| 164 | 0 | var args = Array.prototype.slice.call(arguments, 3); |
| 165 | 0 | callback = args.pop(); |
| 166 | // Fetch all commands | |
| 167 | 0 | reduce = args.length ? args.shift() : null; |
| 168 | 0 | finalize = args.length ? args.shift() : null; |
| 169 | 0 | command = args.length ? args.shift() : null; |
| 170 | 0 | options = args.length ? args.shift() || {} : {}; |
| 171 | ||
| 172 | // Make sure we are backward compatible | |
| 173 | 0 | if(!(typeof finalize == 'function')) { |
| 174 | 0 | command = finalize; |
| 175 | 0 | finalize = null; |
| 176 | } | |
| 177 | ||
| 178 | 0 | if (!Array.isArray(keys) && keys instanceof Object && typeof(keys) !== 'function' && !(keys instanceof Code)) { |
| 179 | 0 | keys = Object.keys(keys); |
| 180 | } | |
| 181 | ||
| 182 | 0 | if(typeof reduce === 'function') { |
| 183 | 0 | reduce = reduce.toString(); |
| 184 | } | |
| 185 | ||
| 186 | 0 | if(typeof finalize === 'function') { |
| 187 | 0 | finalize = finalize.toString(); |
| 188 | } | |
| 189 | ||
| 190 | // Set up the command as default | |
| 191 | 0 | command = command == null ? true : command; |
| 192 | ||
| 193 | // Execute using the command | |
| 194 | 0 | if(command) { |
| 195 | 0 | var reduceFunction = reduce instanceof Code |
| 196 | ? reduce | |
| 197 | : new Code(reduce); | |
| 198 | ||
| 199 | 0 | var selector = { |
| 200 | group: { | |
| 201 | 'ns': this.collectionName | |
| 202 | , '$reduce': reduceFunction | |
| 203 | , 'cond': condition | |
| 204 | , 'initial': initial | |
| 205 | , 'out': "inline" | |
| 206 | } | |
| 207 | }; | |
| 208 | ||
| 209 | // if finalize is defined | |
| 210 | 0 | if(finalize != null) selector.group['finalize'] = finalize; |
| 211 | // Set up group selector | |
| 212 | 0 | if ('function' === typeof keys || keys instanceof Code) { |
| 213 | 0 | selector.group.$keyf = keys instanceof Code |
| 214 | ? keys | |
| 215 | : new Code(keys); | |
| 216 | } else { | |
| 217 | 0 | var hash = {}; |
| 218 | 0 | keys.forEach(function (key) { |
| 219 | 0 | hash[key] = 1; |
| 220 | }); | |
| 221 | 0 | selector.group.key = hash; |
| 222 | } | |
| 223 | ||
| 224 | 0 | var cmd = DbCommand.createDbSlaveOkCommand(this.db, selector); |
| 225 | // Set read preference if we set one | |
| 226 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 227 | // Execute the command | |
| 228 | 0 | this.db._executeQueryCommand(cmd |
| 229 | , {read:readPreference} | |
| 230 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 231 | 0 | if(err) return callback(err, null); |
| 232 | 0 | callback(null, result.retval); |
| 233 | })); | |
| 234 | } else { | |
| 235 | // Create execution scope | |
| 236 | 0 | var scope = reduce != null && reduce instanceof Code |
| 237 | ? reduce.scope | |
| 238 | : {}; | |
| 239 | ||
| 240 | 0 | scope.ns = this.collectionName; |
| 241 | 0 | scope.keys = keys; |
| 242 | 0 | scope.condition = condition; |
| 243 | 0 | scope.initial = initial; |
| 244 | ||
| 245 | // Pass in the function text to execute within mongodb. | |
| 246 | 0 | var groupfn = groupFunction.replace(/ reduce;/, reduce.toString() + ';'); |
| 247 | ||
| 248 | 0 | this.db.eval(new Code(groupfn, scope), function (err, results) { |
| 249 | 0 | if (err) return callback(err, null); |
| 250 | 0 | callback(null, results.result || results); |
| 251 | }); | |
| 252 | } | |
| 253 | }; | |
| 254 | ||
| 255 | 1 | var aggregate = function(pipeline, options, callback) { |
| 256 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 257 | 0 | callback = args.pop(); |
| 258 | 0 | var self = this; |
| 259 | ||
| 260 | // If we have any of the supported options in the options object | |
| 261 | 0 | var opts = args[args.length - 1]; |
| 262 | 0 | options = opts.readPreference |
| 263 | || opts.explain | |
| 264 | || opts.cursor | |
| 265 | || opts.out | |
| 266 | || opts.allowDiskUsage ? args.pop() : {} | |
| 267 | // If the callback is the option (as for cursor override it) | |
| 268 | 0 | if(typeof callback == 'object' && callback != null) options = callback; |
| 269 | ||
| 270 | // Convert operations to an array | |
| 271 | 0 | if(!Array.isArray(args[0])) { |
| 272 | 0 | pipeline = []; |
| 273 | // Push all the operations to the pipeline | |
| 274 | 0 | for(var i = 0; i < args.length; i++) pipeline.push(args[i]); |
| 275 | } | |
| 276 | ||
| 277 | // Is the user requesting a cursor | |
| 278 | 0 | if(options.cursor != null && options.out == null) { |
| 279 | // Set the aggregation cursor options | |
| 280 | 0 | var agg_cursor_options = options.cursor; |
| 281 | 0 | agg_cursor_options.pipe = pipeline; |
| 282 | 0 | agg_cursor_options.allowDiskUsage = options.allowDiskUsage == null ? false : options.allowDiskUsage; |
| 283 | // Return the aggregation cursor | |
| 284 | 0 | return new AggregationCursor(this, this.serverCapabilities, agg_cursor_options); |
| 285 | } | |
| 286 | ||
| 287 | // If out was specified | |
| 288 | 0 | if(typeof options.out == 'string') { |
| 289 | 0 | pipeline.push({$out: options.out}); |
| 290 | } | |
| 291 | ||
| 292 | // Build the command | |
| 293 | 0 | var command = { aggregate : this.collectionName, pipeline : pipeline}; |
| 294 | // If we have allowDiskUsage defined | |
| 295 | 0 | if(options.allowDiskUsage) command.allowDiskUsage = options.allowDiskUsage; |
| 296 | ||
| 297 | // Ensure we have the right read preference inheritance | |
| 298 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 299 | // If explain has been specified add it | |
| 300 | 0 | if(options.explain) command.explain = options.explain; |
| 301 | // Execute the command | |
| 302 | 0 | this.db.command(command, options, function(err, result) { |
| 303 | 0 | if(err) { |
| 304 | 0 | callback(err); |
| 305 | 0 | } else if(result['err'] || result['errmsg']) { |
| 306 | 0 | callback(utils.toError(result)); |
| 307 | 0 | } else if(typeof result == 'object' && result['serverPipeline']) { |
| 308 | 0 | callback(null, result['serverPipeline']); |
| 309 | 0 | } else if(typeof result == 'object' && result['stages']) { |
| 310 | 0 | callback(null, result['stages']); |
| 311 | } else { | |
| 312 | 0 | callback(null, result.result); |
| 313 | } | |
| 314 | }); | |
| 315 | } | |
| 316 | ||
| 317 | 1 | exports.mapReduce = mapReduce; |
| 318 | 1 | exports.group = group; |
| 319 | 1 | exports.aggregate = aggregate; |
| 320 | 1 | exports.pipe = pipe; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils') | |
| 3 | , DbCommand = require('../commands/db_command').DbCommand; | |
| 4 | ||
| 5 | 1 | var stats = function stats(options, callback) { |
| 6 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 7 | 0 | callback = args.pop(); |
| 8 | // Fetch all commands | |
| 9 | 0 | options = args.length ? args.shift() || {} : {}; |
| 10 | ||
| 11 | // Build command object | |
| 12 | 0 | var commandObject = { |
| 13 | collStats:this.collectionName, | |
| 14 | } | |
| 15 | ||
| 16 | // Check if we have the scale value | |
| 17 | 0 | if(options['scale'] != null) commandObject['scale'] = options['scale']; |
| 18 | ||
| 19 | // Ensure we have the right read preference inheritance | |
| 20 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 21 | ||
| 22 | // Execute the command | |
| 23 | 0 | this.db.command(commandObject, options, callback); |
| 24 | } | |
| 25 | ||
| 26 | 1 | var count = function count(query, options, callback) { |
| 27 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 28 | 0 | callback = args.pop(); |
| 29 | 0 | query = args.length ? args.shift() || {} : {}; |
| 30 | 0 | options = args.length ? args.shift() || {} : {}; |
| 31 | 0 | var skip = options.skip; |
| 32 | 0 | var limit = options.limit; |
| 33 | 0 | var maxTimeMS = options.maxTimeMS; |
| 34 | ||
| 35 | // Final query | |
| 36 | 0 | var commandObject = { |
| 37 | 'count': this.collectionName | |
| 38 | , 'query': query | |
| 39 | , 'fields': null | |
| 40 | }; | |
| 41 | ||
| 42 | // Add limit and skip if defined | |
| 43 | 0 | if(typeof skip == 'number') commandObject.skip = skip; |
| 44 | 0 | if(typeof limit == 'number') commandObject.limit = limit; |
| 45 | 0 | if(typeof maxTimeMS == 'number') commandObject['$maxTimeMS'] = maxTimeMS; |
| 46 | ||
| 47 | // Set read preference if we set one | |
| 48 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 49 | // Execute the command | |
| 50 | 0 | this.db._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this.db, commandObject) |
| 51 | , {read: readPreference} | |
| 52 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 53 | 0 | if(err) return callback(err, null); |
| 54 | 0 | if(result == null) return callback(new Error("no result returned for count"), null); |
| 55 | 0 | callback(null, result.n); |
| 56 | })); | |
| 57 | }; | |
| 58 | ||
| 59 | 1 | var distinct = function distinct(key, query, options, callback) { |
| 60 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 61 | 0 | callback = args.pop(); |
| 62 | 0 | query = args.length ? args.shift() || {} : {}; |
| 63 | 0 | options = args.length ? args.shift() || {} : {}; |
| 64 | ||
| 65 | 0 | var mapCommandHash = { |
| 66 | 'distinct': this.collectionName | |
| 67 | , 'query': query | |
| 68 | , 'key': key | |
| 69 | }; | |
| 70 | ||
| 71 | // Set read preference if we set one | |
| 72 | 0 | var readPreference = options['readPreference'] ? options['readPreference'] : false; |
| 73 | // Create the command | |
| 74 | 0 | var cmd = DbCommand.createDbSlaveOkCommand(this.db, mapCommandHash); |
| 75 | ||
| 76 | 0 | this.db._executeQueryCommand(cmd, {read:readPreference}, function (err, result) { |
| 77 | 0 | if(err) |
| 78 | 0 | return callback(err); |
| 79 | 0 | if(result.documents[0].ok != 1) |
| 80 | 0 | return callback(new Error(result.documents[0].errmsg)); |
| 81 | 0 | callback(null, result.documents[0].values); |
| 82 | }); | |
| 83 | }; | |
| 84 | ||
| 85 | 1 | var rename = function rename (newName, options, callback) { |
| 86 | 0 | var self = this; |
| 87 | 0 | if(typeof options == 'function') { |
| 88 | 0 | callback = options; |
| 89 | 0 | options = {} |
| 90 | } | |
| 91 | ||
| 92 | // Get collection class | |
| 93 | 0 | var Collection = require('../collection').Collection; |
| 94 | // Ensure the new name is valid | |
| 95 | 0 | shared.checkCollectionName(newName); |
| 96 | ||
| 97 | // Execute the command, return the new renamed collection if successful | |
| 98 | 0 | self.db._executeQueryCommand(DbCommand.createRenameCollectionCommand(self.db, self.collectionName, newName, options) |
| 99 | , utils.handleSingleCommandResultReturn(true, false, function(err, result) { | |
| 100 | 0 | if(err) return callback(err, null) |
| 101 | 0 | try { |
| 102 | 0 | if(options.new_collection) |
| 103 | 0 | return callback(null, new Collection(self.db, newName, self.db.pkFactory)); |
| 104 | 0 | self.collectionName = newName; |
| 105 | 0 | callback(null, self); |
| 106 | } catch(err) { | |
| 107 | 0 | callback(err, null); |
| 108 | } | |
| 109 | })); | |
| 110 | }; | |
| 111 | ||
| 112 | 1 | var options = function options(callback) { |
| 113 | 0 | this.db.collectionsInfo(this.collectionName, function (err, cursor) { |
| 114 | 0 | if (err) return callback(err); |
| 115 | 0 | cursor.nextObject(function (err, document) { |
| 116 | 0 | callback(err, document && document.options || null); |
| 117 | }); | |
| 118 | }); | |
| 119 | }; | |
| 120 | ||
| 121 | 1 | var isCapped = function isCapped(callback) { |
| 122 | 0 | this.options(function(err, document) { |
| 123 | 0 | if(err != null) { |
| 124 | 0 | callback(err); |
| 125 | } else { | |
| 126 | 0 | callback(null, document && document.capped); |
| 127 | } | |
| 128 | }); | |
| 129 | }; | |
| 130 | ||
| 131 | 1 | exports.stats = stats; |
| 132 | 1 | exports.count = count; |
| 133 | 1 | exports.distinct = distinct; |
| 134 | 1 | exports.rename = rename; |
| 135 | 1 | exports.options = options; |
| 136 | 1 | exports.isCapped = isCapped; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var InsertCommand = require('../commands/insert_command').InsertCommand |
| 2 | , DeleteCommand = require('../commands/delete_command').DeleteCommand | |
| 3 | , UpdateCommand = require('../commands/update_command').UpdateCommand | |
| 4 | , DbCommand = require('../commands/db_command').DbCommand | |
| 5 | , utils = require('../utils') | |
| 6 | , hasWriteCommands = require('../utils').hasWriteCommands | |
| 7 | , shared = require('./shared'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Precompiled regexes | |
| 11 | * @ignore | |
| 12 | **/ | |
| 13 | 1 | var eErrorMessages = /No matching object found/; |
| 14 | ||
| 15 | // *************************************************** | |
| 16 | // Insert function | |
| 17 | // *************************************************** | |
| 18 | 1 | var insert = function insert (docs, options, callback) { |
| 19 | 0 | if ('function' === typeof options) callback = options, options = {}; |
| 20 | 0 | if(options == null) options = {}; |
| 21 | 0 | if(!('function' === typeof callback)) callback = null; |
| 22 | ||
| 23 | // Get a connection | |
| 24 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 25 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 26 | // If we support write commands let's perform the insert using it | |
| 27 | 0 | if(!useLegacyOps && hasWriteCommands(connection) |
| 28 | && !Buffer.isBuffer(docs) | |
| 29 | && (!Array.isArray(docs) && docs.length > 0 && !Buffer.isBuffer(docs[0]))) { | |
| 30 | 0 | insertWithWriteCommands(this, Array.isArray(docs) ? docs : [docs], options, callback); |
| 31 | 0 | return this |
| 32 | } | |
| 33 | ||
| 34 | // Backwards compatibility | |
| 35 | 0 | insertAll(this, Array.isArray(docs) ? docs : [docs], options, callback); |
| 36 | 0 | return this; |
| 37 | }; | |
| 38 | ||
| 39 | // | |
| 40 | // Uses the new write commands available from 2.6 > | |
| 41 | // | |
| 42 | 1 | var insertWithWriteCommands = function(self, docs, options, callback) { |
| 43 | // Get the intended namespace for the operation | |
| 44 | 0 | var namespace = self.collectionName; |
| 45 | ||
| 46 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 47 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 48 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 49 | } | |
| 50 | ||
| 51 | // Check if we have passed in continue on error | |
| 52 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 53 | ? options['keepGoing'] : false; | |
| 54 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 55 | ? options['continueOnError'] : continueOnError; | |
| 56 | ||
| 57 | // Do we serialzie functions | |
| 58 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 59 | ? self.serializeFunctions : options.serializeFunctions; | |
| 60 | ||
| 61 | // Checkout a write connection | |
| 62 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 63 | ||
| 64 | // Collect errorOptions | |
| 65 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 66 | ||
| 67 | // If we have a write command with no callback and w:0 fail | |
| 68 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 69 | 0 | throw new Error("writeConcern requires callback") |
| 70 | } | |
| 71 | ||
| 72 | // Add the documents and decorate them with id's if they have none | |
| 73 | 0 | for(var index = 0, len = docs.length; index < len; ++index) { |
| 74 | 0 | var doc = docs[index]; |
| 75 | ||
| 76 | // Add id to each document if it's not already defined | |
| 77 | 0 | if (!(Buffer.isBuffer(doc)) |
| 78 | && doc['_id'] == null | |
| 79 | && self.db.forceServerObjectId != true | |
| 80 | && options.forceServerObjectId != true) { | |
| 81 | 0 | doc['_id'] = self.pkFactory.createPk(); |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | // Create the write command | |
| 86 | 0 | var write_command = { |
| 87 | insert: namespace | |
| 88 | , writeConcern: errorOptions | |
| 89 | , ordered: !continueOnError | |
| 90 | , documents: docs | |
| 91 | } | |
| 92 | ||
| 93 | // Execute the write command | |
| 94 | 0 | self.db.command(write_command |
| 95 | , { connection:connection | |
| 96 | , checkKeys: true | |
| 97 | , serializeFunctions: serializeFunctions | |
| 98 | , writeCommand: true } | |
| 99 | , function(err, result) { | |
| 100 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 101 | 0 | if(errorOptions.w == 0) return; |
| 102 | 0 | if(callback == null) return; |
| 103 | 0 | if(err != null) { |
| 104 | // Rewrite for backward compatibility | |
| 105 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 106 | // Return the error | |
| 107 | 0 | return callback(err, null); |
| 108 | } | |
| 109 | ||
| 110 | // Result has an error | |
| 111 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 112 | // Map the error | |
| 113 | 0 | var error = utils.toError(result); |
| 114 | // Backwards compatibility mapping | |
| 115 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 116 | // Return the error | |
| 117 | 0 | return callback(error, null); |
| 118 | } | |
| 119 | ||
| 120 | // Return the results for a whole batch | |
| 121 | 0 | callback(null, docs) |
| 122 | }); | |
| 123 | } | |
| 124 | ||
| 125 | // | |
| 126 | // Uses pre 2.6 OP_INSERT wire protocol | |
| 127 | // | |
| 128 | 1 | var insertAll = function insertAll (self, docs, options, callback) { |
| 129 | 0 | if('function' === typeof options) callback = options, options = {}; |
| 130 | 0 | if(options == null) options = {}; |
| 131 | 0 | if(!('function' === typeof callback)) callback = null; |
| 132 | ||
| 133 | // Insert options (flags for insert) | |
| 134 | 0 | var insertFlags = {}; |
| 135 | // If we have a mongodb version >= 1.9.1 support keepGoing attribute | |
| 136 | 0 | if(options['keepGoing'] != null) { |
| 137 | 0 | insertFlags['keepGoing'] = options['keepGoing']; |
| 138 | } | |
| 139 | ||
| 140 | // If we have a mongodb version >= 1.9.1 support keepGoing attribute | |
| 141 | 0 | if(options['continueOnError'] != null) { |
| 142 | 0 | insertFlags['continueOnError'] = options['continueOnError']; |
| 143 | } | |
| 144 | ||
| 145 | // DbName | |
| 146 | 0 | var dbName = options['dbName']; |
| 147 | // If no dbname defined use the db one | |
| 148 | 0 | if(dbName == null) { |
| 149 | 0 | dbName = self.db.databaseName; |
| 150 | } | |
| 151 | ||
| 152 | // Either use override on the function, or go back to default on either the collection | |
| 153 | // level or db | |
| 154 | 0 | if(options['serializeFunctions'] != null) { |
| 155 | 0 | insertFlags['serializeFunctions'] = options['serializeFunctions']; |
| 156 | } else { | |
| 157 | 0 | insertFlags['serializeFunctions'] = self.serializeFunctions; |
| 158 | } | |
| 159 | ||
| 160 | // Get checkKeys value | |
| 161 | 0 | var checkKeys = typeof options.checkKeys != 'boolean' ? true : options.checkKeys; |
| 162 | ||
| 163 | // Pass in options | |
| 164 | 0 | var insertCommand = new InsertCommand( |
| 165 | self.db | |
| 166 | , dbName + "." + self.collectionName, checkKeys, insertFlags); | |
| 167 | ||
| 168 | // Add the documents and decorate them with id's if they have none | |
| 169 | 0 | for(var index = 0, len = docs.length; index < len; ++index) { |
| 170 | 0 | var doc = docs[index]; |
| 171 | ||
| 172 | // Add id to each document if it's not already defined | |
| 173 | 0 | if (!(Buffer.isBuffer(doc)) |
| 174 | && doc['_id'] == null | |
| 175 | && self.db.forceServerObjectId != true | |
| 176 | && options.forceServerObjectId != true) { | |
| 177 | 0 | doc['_id'] = self.pkFactory.createPk(); |
| 178 | } | |
| 179 | ||
| 180 | 0 | insertCommand.add(doc); |
| 181 | } | |
| 182 | ||
| 183 | // Collect errorOptions | |
| 184 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 185 | // Default command options | |
| 186 | 0 | var commandOptions = {}; |
| 187 | // If safe is defined check for error message | |
| 188 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 189 | // Insert options | |
| 190 | 0 | commandOptions['read'] = false; |
| 191 | // If we have safe set set async to false | |
| 192 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 193 | ||
| 194 | // Set safe option | |
| 195 | 0 | commandOptions['safe'] = errorOptions; |
| 196 | // If we have an error option | |
| 197 | 0 | if(typeof errorOptions == 'object') { |
| 198 | 0 | var keys = Object.keys(errorOptions); |
| 199 | 0 | for(var i = 0; i < keys.length; i++) { |
| 200 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 205 | 0 | self.db._executeInsertCommand(insertCommand, commandOptions, function (err, error) { |
| 206 | 0 | error = error && error.documents; |
| 207 | 0 | if(!callback) return; |
| 208 | ||
| 209 | 0 | if (err) { |
| 210 | 0 | callback(err); |
| 211 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 212 | 0 | callback(utils.toError(error[0])); |
| 213 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 214 | 0 | callback(utils.toError(error[0])); |
| 215 | } else { | |
| 216 | 0 | callback(null, docs); |
| 217 | } | |
| 218 | }); | |
| 219 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 220 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 221 | } else { | |
| 222 | // Execute the call without a write concern | |
| 223 | 0 | var result = self.db._executeInsertCommand(insertCommand, commandOptions); |
| 224 | // If no callback just return | |
| 225 | 0 | if(!callback) return; |
| 226 | // If error return error | |
| 227 | 0 | if(result instanceof Error) { |
| 228 | 0 | return callback(result); |
| 229 | } | |
| 230 | ||
| 231 | // Otherwise just return | |
| 232 | 0 | return callback(null, docs); |
| 233 | } | |
| 234 | }; | |
| 235 | ||
| 236 | // *************************************************** | |
| 237 | // Remove function | |
| 238 | // *************************************************** | |
| 239 | 1 | var removeWithWriteCommands = function(self, selector, options, callback) { |
| 240 | 0 | if ('function' === typeof selector) { |
| 241 | 0 | callback = selector; |
| 242 | 0 | selector = options = {}; |
| 243 | 0 | } else if ('function' === typeof options) { |
| 244 | 0 | callback = options; |
| 245 | 0 | options = {}; |
| 246 | } | |
| 247 | ||
| 248 | // Get the intended namespace for the operation | |
| 249 | 0 | var namespace = self.collectionName; |
| 250 | ||
| 251 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 252 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 253 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 254 | } | |
| 255 | ||
| 256 | // Set default empty selector if none | |
| 257 | 0 | selector = selector == null ? {} : selector; |
| 258 | ||
| 259 | // Check if we have passed in continue on error | |
| 260 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 261 | ? options['keepGoing'] : false; | |
| 262 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 263 | ? options['continueOnError'] : continueOnError; | |
| 264 | ||
| 265 | // Do we serialzie functions | |
| 266 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 267 | ? self.serializeFunctions : options.serializeFunctions; | |
| 268 | ||
| 269 | // Checkout a write connection | |
| 270 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 271 | ||
| 272 | // Figure out the value of top | |
| 273 | 0 | var limit = options.single == true ? 1 : 0; |
| 274 | 0 | var upsert = typeof options.upsert == 'boolean' ? options.upsert : false; |
| 275 | ||
| 276 | // Collect errorOptions | |
| 277 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 278 | ||
| 279 | // If we have a write command with no callback and w:0 fail | |
| 280 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 281 | 0 | throw new Error("writeConcern requires callback") |
| 282 | } | |
| 283 | ||
| 284 | // Create the write command | |
| 285 | 0 | var write_command = { |
| 286 | delete: namespace, | |
| 287 | writeConcern: errorOptions, | |
| 288 | ordered: !continueOnError, | |
| 289 | deletes: [{ | |
| 290 | q : selector, | |
| 291 | limit: limit | |
| 292 | }] | |
| 293 | } | |
| 294 | ||
| 295 | // Execute the write command | |
| 296 | 0 | self.db.command(write_command |
| 297 | , { connection:connection | |
| 298 | , checkKeys: true | |
| 299 | , serializeFunctions: serializeFunctions | |
| 300 | , writeCommand: true } | |
| 301 | , function(err, result) { | |
| 302 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 303 | 0 | if(errorOptions.w == 0) return; |
| 304 | 0 | if(callback == null) return; |
| 305 | 0 | if(err != null) { |
| 306 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 307 | // Return the error | |
| 308 | 0 | return callback(err, null); |
| 309 | } | |
| 310 | ||
| 311 | // Result has an error | |
| 312 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 313 | // Map the error | |
| 314 | 0 | var error = utils.toError(result); |
| 315 | // Backwards compatibility mapping | |
| 316 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 317 | // Return the error | |
| 318 | 0 | return callback(error, null); |
| 319 | } | |
| 320 | ||
| 321 | // Backward compatibility format | |
| 322 | 0 | var r = backWardsCompatibiltyResults(result, 'remove'); |
| 323 | // Return the results for a whole batch | |
| 324 | 0 | callback(null, r.n, r) |
| 325 | }); | |
| 326 | } | |
| 327 | ||
| 328 | 1 | var remove = function remove(selector, options, callback) { |
| 329 | 0 | if('function' === typeof options) callback = options, options = null; |
| 330 | 0 | if(options == null) options = {}; |
| 331 | 0 | if(!('function' === typeof callback)) callback = null; |
| 332 | // Get a connection | |
| 333 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 334 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 335 | // If we support write commands let's perform the insert using it | |
| 336 | 0 | if(!useLegacyOps && hasWriteCommands(connection) && !Buffer.isBuffer(selector)) { |
| 337 | 0 | return removeWithWriteCommands(this, selector, options, callback); |
| 338 | } | |
| 339 | ||
| 340 | 0 | if ('function' === typeof selector) { |
| 341 | 0 | callback = selector; |
| 342 | 0 | selector = options = {}; |
| 343 | 0 | } else if ('function' === typeof options) { |
| 344 | 0 | callback = options; |
| 345 | 0 | options = {}; |
| 346 | } | |
| 347 | ||
| 348 | // Ensure options | |
| 349 | 0 | if(options == null) options = {}; |
| 350 | 0 | if(!('function' === typeof callback)) callback = null; |
| 351 | // Ensure we have at least an empty selector | |
| 352 | 0 | selector = selector == null ? {} : selector; |
| 353 | // Set up flags for the command, if we have a single document remove | |
| 354 | 0 | var flags = 0 | (options.single ? 1 : 0); |
| 355 | ||
| 356 | // DbName | |
| 357 | 0 | var dbName = options['dbName']; |
| 358 | // If no dbname defined use the db one | |
| 359 | 0 | if(dbName == null) { |
| 360 | 0 | dbName = this.db.databaseName; |
| 361 | } | |
| 362 | ||
| 363 | // Create a delete command | |
| 364 | 0 | var deleteCommand = new DeleteCommand( |
| 365 | this.db | |
| 366 | , dbName + "." + this.collectionName | |
| 367 | , selector | |
| 368 | , flags); | |
| 369 | ||
| 370 | 0 | var self = this; |
| 371 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 372 | // Execute the command, do not add a callback as it's async | |
| 373 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 374 | // Insert options | |
| 375 | 0 | var commandOptions = {read:false}; |
| 376 | // If we have safe set set async to false | |
| 377 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 378 | // Set safe option | |
| 379 | 0 | commandOptions['safe'] = true; |
| 380 | // If we have an error option | |
| 381 | 0 | if(typeof errorOptions == 'object') { |
| 382 | 0 | var keys = Object.keys(errorOptions); |
| 383 | 0 | for(var i = 0; i < keys.length; i++) { |
| 384 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 389 | 0 | this.db._executeRemoveCommand(deleteCommand, commandOptions, function (err, error) { |
| 390 | 0 | error = error && error.documents; |
| 391 | 0 | if(!callback) return; |
| 392 | ||
| 393 | 0 | if(err) { |
| 394 | 0 | callback(err); |
| 395 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 396 | 0 | callback(utils.toError(error[0])); |
| 397 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 398 | 0 | callback(utils.toError(error[0])); |
| 399 | } else { | |
| 400 | 0 | callback(null, error[0].n); |
| 401 | } | |
| 402 | }); | |
| 403 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 404 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 405 | } else { | |
| 406 | 0 | var result = this.db._executeRemoveCommand(deleteCommand); |
| 407 | // If no callback just return | |
| 408 | 0 | if (!callback) return; |
| 409 | // If error return error | |
| 410 | 0 | if (result instanceof Error) { |
| 411 | 0 | return callback(result); |
| 412 | } | |
| 413 | // Otherwise just return | |
| 414 | 0 | return callback(); |
| 415 | } | |
| 416 | }; | |
| 417 | ||
| 418 | // *************************************************** | |
| 419 | // Save function | |
| 420 | // *************************************************** | |
| 421 | 1 | var save = function save(doc, options, callback) { |
| 422 | 0 | if('function' === typeof options) callback = options, options = null; |
| 423 | 0 | if(options == null) options = {}; |
| 424 | 0 | if(!('function' === typeof callback)) callback = null; |
| 425 | // Throw an error if attempting to perform a bulk operation | |
| 426 | 0 | if(Array.isArray(doc)) throw new Error("doc parameter must be a single document"); |
| 427 | // Extract the id, if we have one we need to do a update command | |
| 428 | 0 | var id = doc['_id']; |
| 429 | 0 | var commandOptions = shared._getWriteConcern(this, options); |
| 430 | ||
| 431 | 0 | if(id != null) { |
| 432 | 0 | commandOptions.upsert = true; |
| 433 | 0 | this.update({ _id: id }, doc, commandOptions, callback); |
| 434 | } else { | |
| 435 | 0 | this.insert(doc, commandOptions, callback && function (err, docs) { |
| 436 | 0 | if(err) return callback(err, null); |
| 437 | ||
| 438 | 0 | if(Array.isArray(docs)) { |
| 439 | 0 | callback(err, docs[0]); |
| 440 | } else { | |
| 441 | 0 | callback(err, docs); |
| 442 | } | |
| 443 | }); | |
| 444 | } | |
| 445 | }; | |
| 446 | ||
| 447 | // *************************************************** | |
| 448 | // Update document function | |
| 449 | // *************************************************** | |
| 450 | 1 | var updateWithWriteCommands = function(self, selector, document, options, callback) { |
| 451 | 0 | if('function' === typeof options) callback = options, options = null; |
| 452 | 0 | if(options == null) options = {}; |
| 453 | 0 | if(!('function' === typeof callback)) callback = null; |
| 454 | ||
| 455 | // Get the intended namespace for the operation | |
| 456 | 0 | var namespace = self.collectionName; |
| 457 | ||
| 458 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 459 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 460 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 461 | } | |
| 462 | ||
| 463 | // If we are not providing a selector or document throw | |
| 464 | 0 | if(selector == null || typeof selector != 'object') |
| 465 | 0 | return callback(new Error("selector must be a valid JavaScript object")); |
| 466 | 0 | if(document == null || typeof document != 'object') |
| 467 | 0 | return callback(new Error("document must be a valid JavaScript object")); |
| 468 | ||
| 469 | // Check if we have passed in continue on error | |
| 470 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 471 | ? options['keepGoing'] : false; | |
| 472 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 473 | ? options['continueOnError'] : continueOnError; | |
| 474 | ||
| 475 | // Do we serialzie functions | |
| 476 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 477 | ? self.serializeFunctions : options.serializeFunctions; | |
| 478 | ||
| 479 | // Checkout a write connection | |
| 480 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 481 | ||
| 482 | // Figure out the value of top | |
| 483 | 0 | var multi = typeof options.multi == 'boolean' ? options.multi : false; |
| 484 | 0 | var upsert = typeof options.upsert == 'boolean' ? options.upsert : false; |
| 485 | ||
| 486 | // Collect errorOptions | |
| 487 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 488 | ||
| 489 | // If we have a write command with no callback and w:0 fail | |
| 490 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 491 | 0 | throw new Error("writeConcern requires callback") |
| 492 | } | |
| 493 | ||
| 494 | // Create the write command | |
| 495 | 0 | var write_command = { |
| 496 | update: namespace, | |
| 497 | writeConcern: errorOptions, | |
| 498 | ordered: !continueOnError, | |
| 499 | updates: [{ | |
| 500 | q : selector, | |
| 501 | u: document, | |
| 502 | multi: multi, | |
| 503 | upsert: upsert | |
| 504 | }] | |
| 505 | } | |
| 506 | ||
| 507 | // Check if we have a checkKeys override | |
| 508 | 0 | var checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false; |
| 509 | ||
| 510 | // Execute the write command | |
| 511 | 0 | self.db.command(write_command |
| 512 | , { connection:connection | |
| 513 | , checkKeys: checkKeys | |
| 514 | , serializeFunctions: serializeFunctions | |
| 515 | , writeCommand: true } | |
| 516 | , function(err, result) { | |
| 517 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 518 | 0 | if(errorOptions.w == 0) return; |
| 519 | 0 | if(callback == null) return; |
| 520 | 0 | if(err != null) { |
| 521 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 522 | // Return the error | |
| 523 | 0 | return callback(err, null); |
| 524 | } | |
| 525 | ||
| 526 | // Result has an error | |
| 527 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 528 | // Map the error | |
| 529 | 0 | var error = utils.toError(result); |
| 530 | // Backwards compatibility mapping | |
| 531 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 532 | // Return the error | |
| 533 | 0 | return callback(error, null); |
| 534 | } | |
| 535 | ||
| 536 | // Backward compatibility format | |
| 537 | 0 | var r = backWardsCompatibiltyResults(result, 'update'); |
| 538 | // Return the results for a whole batch | |
| 539 | 0 | callback(null, r.n, r) |
| 540 | }); | |
| 541 | } | |
| 542 | ||
| 543 | 1 | var backWardsCompatibiltyResults = function(result, op) { |
| 544 | // Upserted | |
| 545 | 0 | var upsertedValue = null; |
| 546 | 0 | var finalResult = null; |
| 547 | 0 | var updatedExisting = true; |
| 548 | ||
| 549 | // We have a single document upserted result | |
| 550 | 0 | if(Array.isArray(result.upserted) || result.upserted != null) { |
| 551 | 0 | updatedExisting = false; |
| 552 | 0 | upsertedValue = result.upserted; |
| 553 | } | |
| 554 | ||
| 555 | // Final result | |
| 556 | 0 | if(op == 'remove' || op == 'insert') { |
| 557 | 0 | finalResult = {ok: true, n: result.n} |
| 558 | } else { | |
| 559 | 0 | finalResult = {ok: true, n: result.n, updatedExisting: updatedExisting} |
| 560 | } | |
| 561 | ||
| 562 | 0 | if(upsertedValue != null) finalResult.upserted = upsertedValue; |
| 563 | 0 | return finalResult; |
| 564 | } | |
| 565 | ||
| 566 | 1 | var update = function update(selector, document, options, callback) { |
| 567 | 0 | if('function' === typeof options) callback = options, options = null; |
| 568 | 0 | if(options == null) options = {}; |
| 569 | 0 | if(!('function' === typeof callback)) callback = null; |
| 570 | ||
| 571 | // Get a connection | |
| 572 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 573 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 574 | // If we support write commands let's perform the insert using it | |
| 575 | 0 | if(!useLegacyOps && hasWriteCommands(connection) && !Buffer.isBuffer(selector) && !Buffer.isBuffer(document)) { |
| 576 | 0 | return updateWithWriteCommands(this, selector, document, options, callback); |
| 577 | } | |
| 578 | ||
| 579 | // DbName | |
| 580 | 0 | var dbName = options['dbName']; |
| 581 | // If no dbname defined use the db one | |
| 582 | 0 | if(dbName == null) { |
| 583 | 0 | dbName = this.db.databaseName; |
| 584 | } | |
| 585 | ||
| 586 | // If we are not providing a selector or document throw | |
| 587 | 0 | if(selector == null || typeof selector != 'object') return callback(new Error("selector must be a valid JavaScript object")); |
| 588 | 0 | if(document == null || typeof document != 'object') return callback(new Error("document must be a valid JavaScript object")); |
| 589 | ||
| 590 | // Either use override on the function, or go back to default on either the collection | |
| 591 | // level or db | |
| 592 | 0 | if(options['serializeFunctions'] != null) { |
| 593 | 0 | options['serializeFunctions'] = options['serializeFunctions']; |
| 594 | } else { | |
| 595 | 0 | options['serializeFunctions'] = this.serializeFunctions; |
| 596 | } | |
| 597 | ||
| 598 | // Build the options command | |
| 599 | 0 | var updateCommand = new UpdateCommand( |
| 600 | this.db | |
| 601 | , dbName + "." + this.collectionName | |
| 602 | , selector | |
| 603 | , document | |
| 604 | , options); | |
| 605 | ||
| 606 | 0 | var self = this; |
| 607 | // Unpack the error options if any | |
| 608 | 0 | var errorOptions = shared._getWriteConcern(this, options); |
| 609 | // If safe is defined check for error message | |
| 610 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 611 | // Insert options | |
| 612 | 0 | var commandOptions = {read:false}; |
| 613 | // If we have safe set set async to false | |
| 614 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 615 | // Set safe option | |
| 616 | 0 | commandOptions['safe'] = errorOptions; |
| 617 | // If we have an error option | |
| 618 | 0 | if(typeof errorOptions == 'object') { |
| 619 | 0 | var keys = Object.keys(errorOptions); |
| 620 | 0 | for(var i = 0; i < keys.length; i++) { |
| 621 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 622 | } | |
| 623 | } | |
| 624 | ||
| 625 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 626 | 0 | this.db._executeUpdateCommand(updateCommand, commandOptions, function (err, error) { |
| 627 | 0 | error = error && error.documents; |
| 628 | 0 | if(!callback) return; |
| 629 | ||
| 630 | 0 | if(err) { |
| 631 | 0 | callback(err); |
| 632 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 633 | 0 | callback(utils.toError(error[0])); |
| 634 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 635 | 0 | callback(utils.toError(error[0])); |
| 636 | } else { | |
| 637 | 0 | callback(null, error[0].n, error[0]); |
| 638 | } | |
| 639 | }); | |
| 640 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 641 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 642 | } else { | |
| 643 | // Execute update | |
| 644 | 0 | var result = this.db._executeUpdateCommand(updateCommand); |
| 645 | // If no callback just return | |
| 646 | 0 | if (!callback) return; |
| 647 | // If error return error | |
| 648 | 0 | if (result instanceof Error) { |
| 649 | 0 | return callback(result); |
| 650 | } | |
| 651 | // Otherwise just return | |
| 652 | 0 | return callback(); |
| 653 | } | |
| 654 | }; | |
| 655 | ||
| 656 | // *************************************************** | |
| 657 | // findAndModify function | |
| 658 | // *************************************************** | |
| 659 | 1 | var findAndModify = function findAndModify (query, sort, doc, options, callback) { |
| 660 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 661 | 0 | callback = args.pop(); |
| 662 | 0 | sort = args.length ? args.shift() || [] : []; |
| 663 | 0 | doc = args.length ? args.shift() : null; |
| 664 | 0 | options = args.length ? args.shift() || {} : {}; |
| 665 | 0 | var self = this; |
| 666 | ||
| 667 | 0 | var queryObject = { |
| 668 | 'findandmodify': this.collectionName | |
| 669 | , 'query': query | |
| 670 | , 'sort': utils.formattedOrderClause(sort) | |
| 671 | }; | |
| 672 | ||
| 673 | 0 | queryObject.new = options.new ? 1 : 0; |
| 674 | 0 | queryObject.remove = options.remove ? 1 : 0; |
| 675 | 0 | queryObject.upsert = options.upsert ? 1 : 0; |
| 676 | ||
| 677 | 0 | if (options.fields) { |
| 678 | 0 | queryObject.fields = options.fields; |
| 679 | } | |
| 680 | ||
| 681 | 0 | if (doc && !options.remove) { |
| 682 | 0 | queryObject.update = doc; |
| 683 | } | |
| 684 | ||
| 685 | // Checkout a write connection | |
| 686 | 0 | options.connection = self.db.serverConfig.checkoutWriter(); |
| 687 | ||
| 688 | // Either use override on the function, or go back to default on either the collection | |
| 689 | // level or db | |
| 690 | 0 | if(options['serializeFunctions'] != null) { |
| 691 | 0 | options['serializeFunctions'] = options['serializeFunctions']; |
| 692 | } else { | |
| 693 | 0 | options['serializeFunctions'] = this.serializeFunctions; |
| 694 | } | |
| 695 | ||
| 696 | // No check on the documents | |
| 697 | 0 | options.checkKeys = false |
| 698 | ||
| 699 | // Execute the command | |
| 700 | 0 | this.db.command(queryObject |
| 701 | , options, function(err, result) { | |
| 702 | 0 | if(err) return callback(err, null); |
| 703 | 0 | return callback(null, result.value, result); |
| 704 | }); | |
| 705 | } | |
| 706 | ||
| 707 | // *************************************************** | |
| 708 | // findAndRemove function | |
| 709 | // *************************************************** | |
| 710 | 1 | var findAndRemove = function(query, sort, options, callback) { |
| 711 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 712 | 0 | callback = args.pop(); |
| 713 | 0 | sort = args.length ? args.shift() || [] : []; |
| 714 | 0 | options = args.length ? args.shift() || {} : {}; |
| 715 | // Add the remove option | |
| 716 | 0 | options['remove'] = true; |
| 717 | // Execute the callback | |
| 718 | 0 | this.findAndModify(query, sort, null, options, callback); |
| 719 | } | |
| 720 | ||
| 721 | // Map methods | |
| 722 | 1 | exports.insert = insert; |
| 723 | 1 | exports.remove = remove; |
| 724 | 1 | exports.save = save; |
| 725 | 1 | exports.update = update; |
| 726 | 1 | exports.findAndModify = findAndModify; |
| 727 | 1 | exports.findAndRemove = findAndRemove; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils'); | |
| 3 | ||
| 4 | 1 | var geoNear = function geoNear(x, y, options, callback) { |
| 5 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 6 | 0 | callback = args.pop(); |
| 7 | // Fetch all commands | |
| 8 | 0 | options = args.length ? args.shift() || {} : {}; |
| 9 | ||
| 10 | // Build command object | |
| 11 | 0 | var commandObject = { |
| 12 | geoNear:this.collectionName, | |
| 13 | near: [x, y] | |
| 14 | } | |
| 15 | ||
| 16 | // Decorate object if any with known properties | |
| 17 | 0 | if(options['num'] != null) commandObject['num'] = options['num']; |
| 18 | 0 | if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; |
| 19 | 0 | if(options['distanceMultiplier'] != null) commandObject['distanceMultiplier'] = options['distanceMultiplier']; |
| 20 | 0 | if(options['query'] != null) commandObject['query'] = options['query']; |
| 21 | 0 | if(options['spherical'] != null) commandObject['spherical'] = options['spherical']; |
| 22 | 0 | if(options['uniqueDocs'] != null) commandObject['uniqueDocs'] = options['uniqueDocs']; |
| 23 | 0 | if(options['includeLocs'] != null) commandObject['includeLocs'] = options['includeLocs']; |
| 24 | ||
| 25 | // Ensure we have the right read preference inheritance | |
| 26 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 27 | ||
| 28 | // Execute the command | |
| 29 | 0 | this.db.command(commandObject, options, function (err, res) { |
| 30 | 0 | if (err) { |
| 31 | 0 | callback(err); |
| 32 | 0 | } else if (res.err || res.errmsg) { |
| 33 | 0 | callback(utils.toError(res)); |
| 34 | } else { | |
| 35 | // should we only be returning res.results here? Not sure if the user | |
| 36 | // should see the other return information | |
| 37 | 0 | callback(null, res); |
| 38 | } | |
| 39 | }); | |
| 40 | } | |
| 41 | ||
| 42 | 1 | var geoHaystackSearch = function geoHaystackSearch(x, y, options, callback) { |
| 43 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 44 | 0 | callback = args.pop(); |
| 45 | // Fetch all commands | |
| 46 | 0 | options = args.length ? args.shift() || {} : {}; |
| 47 | ||
| 48 | // Build command object | |
| 49 | 0 | var commandObject = { |
| 50 | geoSearch:this.collectionName, | |
| 51 | near: [x, y] | |
| 52 | } | |
| 53 | ||
| 54 | // Decorate object if any with known properties | |
| 55 | 0 | if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; |
| 56 | 0 | if(options['query'] != null) commandObject['search'] = options['query']; |
| 57 | 0 | if(options['search'] != null) commandObject['search'] = options['search']; |
| 58 | 0 | if(options['limit'] != null) commandObject['limit'] = options['limit']; |
| 59 | ||
| 60 | // Ensure we have the right read preference inheritance | |
| 61 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 62 | ||
| 63 | // Execute the command | |
| 64 | 0 | this.db.command(commandObject, options, function (err, res) { |
| 65 | 0 | if (err) { |
| 66 | 0 | callback(err); |
| 67 | 0 | } else if (res.err || res.errmsg) { |
| 68 | 0 | callback(utils.toError(res)); |
| 69 | } else { | |
| 70 | // should we only be returning res.results here? Not sure if the user | |
| 71 | // should see the other return information | |
| 72 | 0 | callback(null, res); |
| 73 | } | |
| 74 | }); | |
| 75 | } | |
| 76 | ||
| 77 | 1 | exports.geoNear = geoNear; |
| 78 | 1 | exports.geoHaystackSearch = geoHaystackSearch; |
| 79 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var _getWriteConcern = require('./shared')._getWriteConcern; |
| 2 | ||
| 3 | 1 | var createIndex = function createIndex (fieldOrSpec, options, callback) { |
| 4 | // Clean up call | |
| 5 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 6 | 0 | callback = args.pop(); |
| 7 | 0 | options = args.length ? args.shift() || {} : {}; |
| 8 | 0 | options = typeof callback === 'function' ? options : callback; |
| 9 | 0 | options = options == null ? {} : options; |
| 10 | ||
| 11 | // Collect errorOptions | |
| 12 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 13 | // Execute create index | |
| 14 | 0 | this.db.createIndex(this.collectionName, fieldOrSpec, options, callback); |
| 15 | }; | |
| 16 | ||
| 17 | 1 | var indexExists = function indexExists(indexes, callback) { |
| 18 | 0 | this.indexInformation(function(err, indexInformation) { |
| 19 | // If we have an error return | |
| 20 | 0 | if(err != null) return callback(err, null); |
| 21 | // Let's check for the index names | |
| 22 | 0 | if(Array.isArray(indexes)) { |
| 23 | 0 | for(var i = 0; i < indexes.length; i++) { |
| 24 | 0 | if(indexInformation[indexes[i]] == null) { |
| 25 | 0 | return callback(null, false); |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | // All keys found return true | |
| 30 | 0 | return callback(null, true); |
| 31 | } else { | |
| 32 | 0 | return callback(null, indexInformation[indexes] != null); |
| 33 | } | |
| 34 | }); | |
| 35 | } | |
| 36 | ||
| 37 | 1 | var dropAllIndexes = function dropIndexes (callback) { |
| 38 | 0 | this.db.dropIndex(this.collectionName, '*', function (err, result) { |
| 39 | 0 | if(err) return callback(err, false); |
| 40 | 0 | callback(null, true); |
| 41 | }); | |
| 42 | }; | |
| 43 | ||
| 44 | 1 | var indexInformation = function indexInformation (options, callback) { |
| 45 | // Unpack calls | |
| 46 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 47 | 0 | callback = args.pop(); |
| 48 | 0 | options = args.length ? args.shift() || {} : {}; |
| 49 | // Call the index information | |
| 50 | 0 | this.db.indexInformation(this.collectionName, options, callback); |
| 51 | }; | |
| 52 | ||
| 53 | 1 | var ensureIndex = function ensureIndex (fieldOrSpec, options, callback) { |
| 54 | // Clean up call | |
| 55 | 0 | if (typeof callback === 'undefined' && typeof options === 'function') { |
| 56 | 0 | callback = options; |
| 57 | 0 | options = {}; |
| 58 | } | |
| 59 | ||
| 60 | 0 | if (options == null) { |
| 61 | 0 | options = {}; |
| 62 | } | |
| 63 | ||
| 64 | // Execute create index | |
| 65 | 0 | this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback); |
| 66 | }; | |
| 67 | ||
| 68 | 1 | exports.createIndex = createIndex; |
| 69 | 1 | exports.indexExists = indexExists; |
| 70 | 1 | exports.dropAllIndexes = dropAllIndexes; |
| 71 | 1 | exports.indexInformation = indexInformation; |
| 72 | 1 | exports.ensureIndex = ensureIndex; |
| 73 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ObjectID = require('bson').ObjectID |
| 2 | , Scope = require('../scope').Scope | |
| 3 | , shared = require('./shared') | |
| 4 | , utils = require('../utils'); | |
| 5 | ||
| 6 | 1 | var testForFields = { |
| 7 | limit: 1, sort: 1, fields:1, skip: 1, hint: 1, explain: 1, snapshot: 1, timeout: 1, tailable: 1, tailableRetryInterval: 1 | |
| 8 | , numberOfRetries: 1, awaitdata: 1, exhaust: 1, batchSize: 1, returnKey: 1, maxScan: 1, min: 1, max: 1, showDiskLoc: 1 | |
| 9 | , comment: 1, raw: 1, readPreference: 1, partial: 1, read: 1, dbName: 1, oplogReplay: 1 | |
| 10 | }; | |
| 11 | ||
| 12 | // | |
| 13 | // Find method | |
| 14 | // | |
| 15 | 1 | var find = function find () { |
| 16 | 0 | var options |
| 17 | , args = Array.prototype.slice.call(arguments, 0) | |
| 18 | , has_callback = typeof args[args.length - 1] === 'function' | |
| 19 | , has_weird_callback = typeof args[0] === 'function' | |
| 20 | , callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null) | |
| 21 | , len = args.length | |
| 22 | , selector = len >= 1 ? args[0] : {} | |
| 23 | , fields = len >= 2 ? args[1] : undefined; | |
| 24 | ||
| 25 | 0 | if(len === 1 && has_weird_callback) { |
| 26 | // backwards compat for callback?, options case | |
| 27 | 0 | selector = {}; |
| 28 | 0 | options = args[0]; |
| 29 | } | |
| 30 | ||
| 31 | 0 | if(len === 2 && !Array.isArray(fields)) { |
| 32 | 0 | var fieldKeys = Object.getOwnPropertyNames(fields); |
| 33 | 0 | var is_option = false; |
| 34 | ||
| 35 | 0 | for(var i = 0; i < fieldKeys.length; i++) { |
| 36 | 0 | if(testForFields[fieldKeys[i]] != null) { |
| 37 | 0 | is_option = true; |
| 38 | 0 | break; |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | 0 | if(is_option) { |
| 43 | 0 | options = fields; |
| 44 | 0 | fields = undefined; |
| 45 | } else { | |
| 46 | 0 | options = {}; |
| 47 | } | |
| 48 | 0 | } else if(len === 2 && Array.isArray(fields) && !Array.isArray(fields[0])) { |
| 49 | 0 | var newFields = {}; |
| 50 | // Rewrite the array | |
| 51 | 0 | for(var i = 0; i < fields.length; i++) { |
| 52 | 0 | newFields[fields[i]] = 1; |
| 53 | } | |
| 54 | // Set the fields | |
| 55 | 0 | fields = newFields; |
| 56 | } | |
| 57 | ||
| 58 | 0 | if(3 === len) { |
| 59 | 0 | options = args[2]; |
| 60 | } | |
| 61 | ||
| 62 | // Ensure selector is not null | |
| 63 | 0 | selector = selector == null ? {} : selector; |
| 64 | // Validate correctness off the selector | |
| 65 | 0 | var object = selector; |
| 66 | 0 | if(Buffer.isBuffer(object)) { |
| 67 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 68 | 0 | if(object_size != object.length) { |
| 69 | 0 | var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 70 | 0 | error.name = 'MongoError'; |
| 71 | 0 | throw error; |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | // Validate correctness of the field selector | |
| 76 | 0 | var object = fields; |
| 77 | 0 | if(Buffer.isBuffer(object)) { |
| 78 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 79 | 0 | if(object_size != object.length) { |
| 80 | 0 | var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 81 | 0 | error.name = 'MongoError'; |
| 82 | 0 | throw error; |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | // Check special case where we are using an objectId | |
| 87 | 0 | if(selector instanceof ObjectID || (selector != null && selector._bsontype == 'ObjectID')) { |
| 88 | 0 | selector = {_id:selector}; |
| 89 | } | |
| 90 | ||
| 91 | // If it's a serialized fields field we need to just let it through | |
| 92 | // user be warned it better be good | |
| 93 | 0 | if(options && options.fields && !(Buffer.isBuffer(options.fields))) { |
| 94 | 0 | fields = {}; |
| 95 | ||
| 96 | 0 | if(Array.isArray(options.fields)) { |
| 97 | 0 | if(!options.fields.length) { |
| 98 | 0 | fields['_id'] = 1; |
| 99 | } else { | |
| 100 | 0 | for (var i = 0, l = options.fields.length; i < l; i++) { |
| 101 | 0 | fields[options.fields[i]] = 1; |
| 102 | } | |
| 103 | } | |
| 104 | } else { | |
| 105 | 0 | fields = options.fields; |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | 0 | if (!options) options = {}; |
| 110 | 0 | options.skip = len > 3 ? args[2] : options.skip ? options.skip : 0; |
| 111 | 0 | options.limit = len > 3 ? args[3] : options.limit ? options.limit : 0; |
| 112 | 0 | options.raw = options.raw != null && typeof options.raw === 'boolean' ? options.raw : this.raw; |
| 113 | 0 | options.hint = options.hint != null ? shared.normalizeHintField(options.hint) : this.internalHint; |
| 114 | 0 | options.timeout = len == 5 ? args[4] : typeof options.timeout === 'undefined' ? undefined : options.timeout; |
| 115 | // If we have overridden slaveOk otherwise use the default db setting | |
| 116 | 0 | options.slaveOk = options.slaveOk != null ? options.slaveOk : this.db.slaveOk; |
| 117 | ||
| 118 | // Set option | |
| 119 | 0 | var o = options; |
| 120 | // Support read/readPreference | |
| 121 | 0 | if(o["read"] != null) o["readPreference"] = o["read"]; |
| 122 | // Set the read preference | |
| 123 | 0 | o.read = o["readPreference"] ? o.readPreference : this.readPreference; |
| 124 | // Adjust slave ok if read preference is secondary or secondary only | |
| 125 | 0 | if(o.read == "secondary" || o.read == "secondaryOnly") options.slaveOk = true; |
| 126 | ||
| 127 | // Set the selector | |
| 128 | 0 | o.selector = selector; |
| 129 | ||
| 130 | // Create precursor | |
| 131 | 0 | var scope = new Scope(this, {}, fields, o); |
| 132 | // Callback for backward compatibility | |
| 133 | 0 | if(callback) return callback(null, scope.find(selector)); |
| 134 | // Return the pre cursor object | |
| 135 | 0 | return scope.find(selector); |
| 136 | }; | |
| 137 | ||
| 138 | 1 | var findOne = function findOne () { |
| 139 | 0 | var self = this; |
| 140 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 141 | 0 | var callback = args.pop(); |
| 142 | 0 | var cursor = this.find.apply(this, args).limit(-1).batchSize(1); |
| 143 | ||
| 144 | // Return the item | |
| 145 | 0 | cursor.nextObject(function(err, item) { |
| 146 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 147 | 0 | callback(null, item); |
| 148 | }); | |
| 149 | }; | |
| 150 | ||
| 151 | ||
| 152 | 1 | exports.find = find; |
| 153 | 1 | exports.findOne = findOne; |
| Line | Hits | Source |
|---|---|---|
| 1 | // *************************************************** | |
| 2 | // Write concerns | |
| 3 | // *************************************************** | |
| 4 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 5 | 0 | return errorOptions == true |
| 6 | || errorOptions.w > 0 | |
| 7 | || errorOptions.w == 'majority' | |
| 8 | || errorOptions.j == true | |
| 9 | || errorOptions.journal == true | |
| 10 | || errorOptions.fsync == true | |
| 11 | } | |
| 12 | ||
| 13 | 1 | var _setWriteConcernHash = function(options) { |
| 14 | 0 | var finalOptions = {}; |
| 15 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 16 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 17 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 18 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 19 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 20 | 0 | return finalOptions; |
| 21 | } | |
| 22 | ||
| 23 | 1 | var _getWriteConcern = function(self, options) { |
| 24 | // Final options | |
| 25 | 0 | var finalOptions = {w:1}; |
| 26 | // Local options verification | |
| 27 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 28 | 0 | finalOptions = _setWriteConcernHash(options); |
| 29 | 0 | } else if(typeof options.safe == "boolean") { |
| 30 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 31 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 32 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 33 | 0 | } else if(self.opts.w != null || typeof self.opts.j == 'boolean' || typeof self.opts.journal == 'boolean' || typeof self.opts.fsync == 'boolean') { |
| 34 | 0 | finalOptions = _setWriteConcernHash(self.opts); |
| 35 | 0 | } else if(typeof self.opts.safe == "boolean") { |
| 36 | 0 | finalOptions = {w: (self.opts.safe ? 1 : 0)}; |
| 37 | 0 | } else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') { |
| 38 | 0 | finalOptions = _setWriteConcernHash(self.db.safe); |
| 39 | 0 | } else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') { |
| 40 | 0 | finalOptions = _setWriteConcernHash(self.db.options); |
| 41 | 0 | } else if(typeof self.db.safe == "boolean") { |
| 42 | 0 | finalOptions = {w: (self.db.safe ? 1 : 0)}; |
| 43 | } | |
| 44 | ||
| 45 | // Ensure we don't have an invalid combination of write concerns | |
| 46 | 0 | if(finalOptions.w < 1 |
| 47 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 48 | ||
| 49 | // Return the options | |
| 50 | 0 | return finalOptions; |
| 51 | } | |
| 52 | ||
| 53 | 1 | var _getReadConcern = function(self, options) { |
| 54 | 0 | if(options.readPreference) return options.readPreference; |
| 55 | 0 | if(self.readPreference) return self.readPreference; |
| 56 | 0 | if(self.db.readPreference) return self.readPreference; |
| 57 | 0 | return 'primary'; |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * @ignore | |
| 62 | */ | |
| 63 | 1 | var checkCollectionName = function checkCollectionName (collectionName) { |
| 64 | 0 | if('string' !== typeof collectionName) { |
| 65 | 0 | throw Error("collection name must be a String"); |
| 66 | } | |
| 67 | ||
| 68 | 0 | if(!collectionName || collectionName.indexOf('..') != -1) { |
| 69 | 0 | throw Error("collection names cannot be empty"); |
| 70 | } | |
| 71 | ||
| 72 | 0 | if(collectionName.indexOf('$') != -1 && |
| 73 | collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) { | |
| 74 | 0 | throw Error("collection names must not contain '$'"); |
| 75 | } | |
| 76 | ||
| 77 | 0 | if(collectionName.match(/^\.|\.$/) != null) { |
| 78 | 0 | throw Error("collection names must not start or end with '.'"); |
| 79 | } | |
| 80 | ||
| 81 | // Validate that we are not passing 0x00 in the colletion name | |
| 82 | 0 | if(!!~collectionName.indexOf("\x00")) { |
| 83 | 0 | throw new Error("collection names cannot contain a null character"); |
| 84 | } | |
| 85 | }; | |
| 86 | ||
| 87 | ||
| 88 | /** | |
| 89 | * Normalizes a `hint` argument. | |
| 90 | * | |
| 91 | * @param {String|Object|Array} hint | |
| 92 | * @return {Object} | |
| 93 | * @api private | |
| 94 | */ | |
| 95 | 1 | var normalizeHintField = function normalizeHintField(hint) { |
| 96 | 0 | var finalHint = null; |
| 97 | ||
| 98 | 0 | if(typeof hint == 'string') { |
| 99 | 0 | finalHint = hint; |
| 100 | 0 | } else if(Array.isArray(hint)) { |
| 101 | 0 | finalHint = {}; |
| 102 | ||
| 103 | 0 | hint.forEach(function(param) { |
| 104 | 0 | finalHint[param] = 1; |
| 105 | }); | |
| 106 | 0 | } else if(hint != null && typeof hint == 'object') { |
| 107 | 0 | finalHint = {}; |
| 108 | 0 | for (var name in hint) { |
| 109 | 0 | finalHint[name] = hint[name]; |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | 0 | return finalHint; |
| 114 | }; | |
| 115 | ||
| 116 | 1 | exports._getWriteConcern = _getWriteConcern; |
| 117 | 1 | exports._hasWriteConcern = _hasWriteConcern; |
| 118 | 1 | exports._getReadConcern = _getReadConcern; |
| 119 | 1 | exports.checkCollectionName = checkCollectionName; |
| 120 | 1 | exports.normalizeHintField = normalizeHintField; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('bson').Long |
| 2 | , GetMoreCommand = require('./commands/get_more_command').GetMoreCommand; | |
| 3 | ||
| 4 | 1 | var CommandCursor = function(db, collection, command, options) { |
| 5 | // Ensure empty options if no options passed | |
| 6 | 0 | options = options || {}; |
| 7 | ||
| 8 | // Default cursor id is 0 | |
| 9 | 0 | var cursorId = Long.fromInt(0); |
| 10 | 0 | var zeroCursor = Long.fromInt(0); |
| 11 | 0 | var state = 'init'; |
| 12 | ||
| 13 | // Hardcode batch size | |
| 14 | 0 | command.cursor.batchSize = 1; |
| 15 | ||
| 16 | // BatchSize | |
| 17 | 0 | var batchSize = command.cursor.batchSize || 0; |
| 18 | 0 | var raw = options.raw || false; |
| 19 | 0 | var readPreference = options.readPreference || 'primary'; |
| 20 | ||
| 21 | // Checkout a connection | |
| 22 | 0 | var connection = db.serverConfig.checkoutReader(readPreference); |
| 23 | // MaxTimeMS | |
| 24 | 0 | var maxTimeMS = options.maxTimeMS; |
| 25 | ||
| 26 | // Contains all the items | |
| 27 | 0 | var items = null; |
| 28 | ||
| 29 | // Execute getmore | |
| 30 | 0 | var getMore = function(callback) { |
| 31 | // Resolve more of the cursor using the getMore command | |
| 32 | 0 | var getMoreCommand = new GetMoreCommand(db |
| 33 | , db.databaseName + "." + collection.collectionName | |
| 34 | , batchSize | |
| 35 | , cursorId | |
| 36 | ); | |
| 37 | ||
| 38 | // Set up options | |
| 39 | 0 | var command_options = { connection:connection }; |
| 40 | ||
| 41 | // Execute the getMore Command | |
| 42 | 0 | db._executeQueryCommand(getMoreCommand, command_options, function(err, result) { |
| 43 | 0 | if(err) { |
| 44 | 0 | items = []; |
| 45 | 0 | state = 'closed'; |
| 46 | 0 | return callback(err); |
| 47 | } | |
| 48 | ||
| 49 | // Return all the documents | |
| 50 | 0 | callback(null, result); |
| 51 | }); | |
| 52 | } | |
| 53 | ||
| 54 | 0 | var exhaustGetMore = function(callback) { |
| 55 | 0 | getMore(function(err, result) { |
| 56 | 0 | if(err) { |
| 57 | 0 | items = []; |
| 58 | 0 | state = 'closed'; |
| 59 | 0 | return callback(err, null); |
| 60 | } | |
| 61 | ||
| 62 | // Add the items | |
| 63 | 0 | items = items.concat(result.documents); |
| 64 | ||
| 65 | // Set the cursor id | |
| 66 | 0 | cursorId = result.cursorId; |
| 67 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 68 | ||
| 69 | // If the cursor is done | |
| 70 | 0 | if(result.cursorId.equals(zeroCursor)) { |
| 71 | 0 | return callback(null, items); |
| 72 | } | |
| 73 | ||
| 74 | // Check the cursor id | |
| 75 | 0 | exhaustGetMore(callback); |
| 76 | }); | |
| 77 | } | |
| 78 | ||
| 79 | 0 | var exhaustGetMoreEach = function(callback) { |
| 80 | 0 | getMore(function(err, result) { |
| 81 | 0 | if(err) { |
| 82 | 0 | items = []; |
| 83 | 0 | state = 'closed'; |
| 84 | 0 | return callback(err, null); |
| 85 | } | |
| 86 | ||
| 87 | // Add the items | |
| 88 | 0 | items = result.documents; |
| 89 | ||
| 90 | // Emit all the items in the first batch | |
| 91 | 0 | while(items.length > 0) { |
| 92 | 0 | callback(null, items.shift()); |
| 93 | } | |
| 94 | ||
| 95 | // Set the cursor id | |
| 96 | 0 | cursorId = result.cursorId; |
| 97 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 98 | ||
| 99 | // If the cursor is done | |
| 100 | 0 | if(result.cursorId.equals(zeroCursor)) { |
| 101 | 0 | state = "closed"; |
| 102 | 0 | return callback(null, null); |
| 103 | } | |
| 104 | ||
| 105 | // Check the cursor id | |
| 106 | 0 | exhaustGetMoreEach(callback); |
| 107 | }); | |
| 108 | } | |
| 109 | ||
| 110 | // | |
| 111 | // Get all the elements | |
| 112 | // | |
| 113 | 0 | this.get = function(options, callback) { |
| 114 | 0 | if(typeof options == 'function') { |
| 115 | 0 | callback = options; |
| 116 | 0 | options = {}; |
| 117 | } | |
| 118 | ||
| 119 | // Set the connection to the passed in one if it's provided | |
| 120 | 0 | connection = options.connection ? options.connection : connection; |
| 121 | ||
| 122 | // Command options | |
| 123 | 0 | var _options = {connection:connection}; |
| 124 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 125 | ||
| 126 | // Execute the internal command first | |
| 127 | 0 | db.command(command, _options, function(err, result) { |
| 128 | 0 | if(err) { |
| 129 | 0 | state = 'closed'; |
| 130 | 0 | return callback(err, null); |
| 131 | } | |
| 132 | ||
| 133 | // Retrieve the cursor id | |
| 134 | 0 | cursorId = result.cursor.id; |
| 135 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 136 | ||
| 137 | // Validate cursorId | |
| 138 | 0 | if(cursorId.equals(zeroCursor)) { |
| 139 | 0 | return callback(null, result.cursor.firstBatch); |
| 140 | }; | |
| 141 | ||
| 142 | // Add to the items | |
| 143 | 0 | items = result.cursor.firstBatch; |
| 144 | // Execute the getMore | |
| 145 | 0 | exhaustGetMore(callback); |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | // | |
| 150 | // Iterate over all the items | |
| 151 | // | |
| 152 | 0 | this.each = function(options, callback) { |
| 153 | 0 | if(typeof options == 'function') { |
| 154 | 0 | callback = options; |
| 155 | 0 | options = {}; |
| 156 | } | |
| 157 | ||
| 158 | // If it's a closed cursor return error | |
| 159 | 0 | if(this.isClosed()) return callback(new Error("cursor is closed")); |
| 160 | // Set the connection to the passed in one if it's provided | |
| 161 | 0 | connection = options.connection ? options.connection : connection; |
| 162 | ||
| 163 | // Command options | |
| 164 | 0 | var _options = {connection:connection}; |
| 165 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 166 | ||
| 167 | // Execute the internal command first | |
| 168 | 0 | db.command(command, _options, function(err, result) { |
| 169 | 0 | if(err) { |
| 170 | 0 | state = 'closed'; |
| 171 | 0 | return callback(err, null); |
| 172 | } | |
| 173 | ||
| 174 | // Get all the items | |
| 175 | 0 | items = result.cursor.firstBatch; |
| 176 | ||
| 177 | // Emit all the items in the first batch | |
| 178 | 0 | while(items.length > 0) { |
| 179 | 0 | callback(null, items.shift()); |
| 180 | } | |
| 181 | ||
| 182 | // Retrieve the cursor id | |
| 183 | 0 | cursorId = result.cursor.id; |
| 184 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 185 | ||
| 186 | // If no cursor we just finish up the current batch of items | |
| 187 | 0 | if(cursorId.equals(zeroCursor)) { |
| 188 | 0 | state = 'closed'; |
| 189 | 0 | return callback(null, null); |
| 190 | } | |
| 191 | ||
| 192 | // Emit each until no more getMore's | |
| 193 | 0 | exhaustGetMoreEach(callback); |
| 194 | }); | |
| 195 | } | |
| 196 | ||
| 197 | // | |
| 198 | // Get the next object | |
| 199 | // | |
| 200 | 0 | this.next = function(options, callback) { |
| 201 | 0 | if(typeof options == 'function') { |
| 202 | 0 | callback = options; |
| 203 | 0 | options = {}; |
| 204 | } | |
| 205 | ||
| 206 | // If it's a closed cursor return error | |
| 207 | 0 | if(this.isClosed()) return callback(new Error("cursor is closed")); |
| 208 | ||
| 209 | // Set the connection to the passed in one if it's provided | |
| 210 | 0 | connection = options.connection ? options.connection : connection; |
| 211 | ||
| 212 | // Command options | |
| 213 | 0 | var _options = {connection:connection}; |
| 214 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 215 | ||
| 216 | // Execute the internal command first | |
| 217 | 0 | if(!items) { |
| 218 | 0 | db.command(command, _options, function(err, result) { |
| 219 | 0 | if(err) { |
| 220 | 0 | state = 'closed'; |
| 221 | 0 | return callback(err, null); |
| 222 | } | |
| 223 | ||
| 224 | // Retrieve the cursor id | |
| 225 | 0 | cursorId = result.cursor.id; |
| 226 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 227 | // Get the first batch results | |
| 228 | 0 | items = result.cursor.firstBatch; |
| 229 | // We have items return the first one | |
| 230 | 0 | if(items.length > 0) { |
| 231 | 0 | callback(null, items.shift()); |
| 232 | } else { | |
| 233 | 0 | state = 'closed'; |
| 234 | 0 | callback(null, null); |
| 235 | } | |
| 236 | }); | |
| 237 | 0 | } else if(items.length > 0) { |
| 238 | 0 | callback(null, items.shift()); |
| 239 | 0 | } else if(items.length == 0 && cursorId.equals(zeroCursor)) { |
| 240 | 0 | state = 'closed'; |
| 241 | 0 | callback(null, null); |
| 242 | } else { | |
| 243 | // Execute a getMore | |
| 244 | 0 | getMore(function(err, result) { |
| 245 | 0 | if(err) { |
| 246 | 0 | state = 'closed'; |
| 247 | 0 | return callback(err, null); |
| 248 | } | |
| 249 | ||
| 250 | // Set the cursor id | |
| 251 | 0 | cursorId = result.cursorId; |
| 252 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 253 | ||
| 254 | // Add the items | |
| 255 | 0 | items = items.concat(result.documents); |
| 256 | // If no more items | |
| 257 | 0 | if(items.length == 0) { |
| 258 | 0 | state = 'closed'; |
| 259 | 0 | return callback(null, null); |
| 260 | } | |
| 261 | ||
| 262 | // Return the item | |
| 263 | 0 | return callback(null, items.shift()); |
| 264 | }) | |
| 265 | } | |
| 266 | } | |
| 267 | ||
| 268 | // Validate if the cursor is closed | |
| 269 | 0 | this.isClosed = function() { |
| 270 | 0 | return state == 'closed'; |
| 271 | } | |
| 272 | ||
| 273 | // Allow us to set the MaxTimeMS | |
| 274 | 0 | this.maxTimeMS = function(_maxTimeMS) { |
| 275 | 0 | maxTimeMS = _maxTimeMS; |
| 276 | } | |
| 277 | ||
| 278 | // Close the cursor sending a kill cursor command if needed | |
| 279 | 0 | this.close = function(options, callback) { |
| 280 | 0 | if(typeof options == 'function') { |
| 281 | 0 | callback = options; |
| 282 | 0 | options = {}; |
| 283 | } | |
| 284 | ||
| 285 | // Close the cursor if not needed | |
| 286 | 0 | if(cursorId instanceof Long && cursorId.greaterThan(Long.fromInt(0))) { |
| 287 | 0 | try { |
| 288 | 0 | var command = new KillCursorCommand(this.db, [cursorId]); |
| 289 | // Added an empty callback to ensure we don't throw any null exceptions | |
| 290 | 0 | db._executeQueryCommand(command, {connection:connection}); |
| 291 | } catch(err) {} | |
| 292 | } | |
| 293 | ||
| 294 | // Null out the connection | |
| 295 | 0 | connection = null; |
| 296 | // Reset cursor id | |
| 297 | 0 | cursorId = Long.fromInt(0); |
| 298 | // Set to closed status | |
| 299 | 0 | state = 'closed'; |
| 300 | // Clear out all the items | |
| 301 | 0 | items = null; |
| 302 | ||
| 303 | 0 | if(callback) { |
| 304 | 0 | callback(null, null); |
| 305 | } | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | 1 | exports.CommandCursor = CommandCursor; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | Base object used for common functionality | |
| 3 | **/ | |
| 4 | 1 | var BaseCommand = exports.BaseCommand = function BaseCommand() { |
| 5 | }; | |
| 6 | ||
| 7 | 1 | var id = 1; |
| 8 | 1 | BaseCommand.prototype.getRequestId = function getRequestId() { |
| 9 | 0 | if (!this.requestId) this.requestId = id++; |
| 10 | 0 | return this.requestId; |
| 11 | }; | |
| 12 | ||
| 13 | 1 | BaseCommand.prototype.setMongosReadPreference = function setMongosReadPreference(readPreference, tags) {} |
| 14 | ||
| 15 | 1 | BaseCommand.prototype.updateRequestId = function() { |
| 16 | 0 | this.requestId = id++; |
| 17 | 0 | return this.requestId; |
| 18 | }; | |
| 19 | ||
| 20 | // OpCodes | |
| 21 | 1 | BaseCommand.OP_REPLY = 1; |
| 22 | 1 | BaseCommand.OP_MSG = 1000; |
| 23 | 1 | BaseCommand.OP_UPDATE = 2001; |
| 24 | 1 | BaseCommand.OP_INSERT = 2002; |
| 25 | 1 | BaseCommand.OP_GET_BY_OID = 2003; |
| 26 | 1 | BaseCommand.OP_QUERY = 2004; |
| 27 | 1 | BaseCommand.OP_GET_MORE = 2005; |
| 28 | 1 | BaseCommand.OP_DELETE = 2006; |
| 29 | 1 | BaseCommand.OP_KILL_CURSORS = 2007; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var QueryCommand = require('./query_command').QueryCommand, |
| 2 | InsertCommand = require('./insert_command').InsertCommand, | |
| 3 | inherits = require('util').inherits, | |
| 4 | utils = require('../utils'), | |
| 5 | crypto = require('crypto'); | |
| 6 | ||
| 7 | /** | |
| 8 | Db Command | |
| 9 | **/ | |
| 10 | 1 | var DbCommand = exports.DbCommand = function(dbInstance, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { |
| 11 | 0 | QueryCommand.call(this); |
| 12 | 0 | this.collectionName = collectionName; |
| 13 | 0 | this.queryOptions = queryOptions; |
| 14 | 0 | this.numberToSkip = numberToSkip; |
| 15 | 0 | this.numberToReturn = numberToReturn; |
| 16 | 0 | this.query = query; |
| 17 | 0 | this.returnFieldSelector = returnFieldSelector; |
| 18 | 0 | this.db = dbInstance; |
| 19 | ||
| 20 | // Set the slave ok bit | |
| 21 | 0 | if(this.db && this.db.slaveOk) { |
| 22 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 23 | } | |
| 24 | ||
| 25 | // Make sure we don't get a null exception | |
| 26 | 0 | options = options == null ? {} : options; |
| 27 | ||
| 28 | // Allow for overriding the BSON checkKeys function | |
| 29 | 0 | this.checkKeys = typeof options['checkKeys'] == 'boolean' ? options["checkKeys"] : true; |
| 30 | ||
| 31 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 32 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 33 | 0 | this.serializeFunctions = true; |
| 34 | } | |
| 35 | }; | |
| 36 | ||
| 37 | 1 | inherits(DbCommand, QueryCommand); |
| 38 | ||
| 39 | // Constants | |
| 40 | 1 | DbCommand.SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"; |
| 41 | 1 | DbCommand.SYSTEM_INDEX_COLLECTION = "system.indexes"; |
| 42 | 1 | DbCommand.SYSTEM_PROFILE_COLLECTION = "system.profile"; |
| 43 | 1 | DbCommand.SYSTEM_USER_COLLECTION = "system.users"; |
| 44 | 1 | DbCommand.SYSTEM_COMMAND_COLLECTION = "$cmd"; |
| 45 | 1 | DbCommand.SYSTEM_JS_COLLECTION = "system.js"; |
| 46 | ||
| 47 | // New commands | |
| 48 | 1 | DbCommand.NcreateIsMasterCommand = function(db, databaseName) { |
| 49 | 0 | return new DbCommand(db, databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); |
| 50 | }; | |
| 51 | ||
| 52 | // Provide constructors for different db commands | |
| 53 | 1 | DbCommand.createIsMasterCommand = function(db) { |
| 54 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); |
| 55 | }; | |
| 56 | ||
| 57 | 1 | DbCommand.createCollectionInfoCommand = function(db, selector) { |
| 58 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_NAMESPACE_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, 0, selector, null); |
| 59 | }; | |
| 60 | ||
| 61 | 1 | DbCommand.createGetNonceCommand = function(db, options) { |
| 62 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getnonce':1}, null); |
| 63 | }; | |
| 64 | ||
| 65 | 1 | DbCommand.createAuthenticationCommand = function(db, username, password, nonce, authdb) { |
| 66 | // Use node md5 generator | |
| 67 | 0 | var md5 = crypto.createHash('md5'); |
| 68 | // Generate keys used for authentication | |
| 69 | 0 | md5.update(username + ":mongo:" + password); |
| 70 | 0 | var hash_password = md5.digest('hex'); |
| 71 | // Final key | |
| 72 | 0 | md5 = crypto.createHash('md5'); |
| 73 | 0 | md5.update(nonce + username + hash_password); |
| 74 | 0 | var key = md5.digest('hex'); |
| 75 | // Creat selector | |
| 76 | 0 | var selector = {'authenticate':1, 'user':username, 'nonce':nonce, 'key':key}; |
| 77 | // Create db command | |
| 78 | 0 | return new DbCommand(db, authdb + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NONE, 0, -1, selector, null); |
| 79 | }; | |
| 80 | ||
| 81 | 1 | DbCommand.createLogoutCommand = function(db) { |
| 82 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'logout':1}, null); |
| 83 | }; | |
| 84 | ||
| 85 | 1 | DbCommand.createCreateCollectionCommand = function(db, collectionName, options) { |
| 86 | 0 | var selector = {'create':collectionName}; |
| 87 | // Modify the options to ensure correct behaviour | |
| 88 | 0 | for(var name in options) { |
| 89 | 0 | if(options[name] != null && options[name].constructor != Function) selector[name] = options[name]; |
| 90 | } | |
| 91 | // Execute the command | |
| 92 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, selector, null); |
| 93 | }; | |
| 94 | ||
| 95 | 1 | DbCommand.createDropCollectionCommand = function(db, collectionName) { |
| 96 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'drop':collectionName}, null); |
| 97 | }; | |
| 98 | ||
| 99 | 1 | DbCommand.createRenameCollectionCommand = function(db, fromCollectionName, toCollectionName, options) { |
| 100 | 0 | var renameCollection = db.databaseName + "." + fromCollectionName; |
| 101 | 0 | var toCollection = db.databaseName + "." + toCollectionName; |
| 102 | 0 | var dropTarget = options && options.dropTarget ? options.dropTarget : false; |
| 103 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'renameCollection':renameCollection, 'to':toCollection, 'dropTarget':dropTarget}, null); |
| 104 | }; | |
| 105 | ||
| 106 | 1 | DbCommand.createGetLastErrorCommand = function(options, db) { |
| 107 | ||
| 108 | 0 | if (typeof db === 'undefined') { |
| 109 | 0 | db = options; |
| 110 | 0 | options = {}; |
| 111 | } | |
| 112 | // Final command | |
| 113 | 0 | var command = {'getlasterror':1}; |
| 114 | // If we have an options Object let's merge in the fields (fsync/wtimeout/w) | |
| 115 | 0 | if('object' === typeof options) { |
| 116 | 0 | for(var name in options) { |
| 117 | 0 | command[name] = options[name] |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | // Special case for w == 1, remove the w | |
| 122 | 0 | if(1 == command.w) { |
| 123 | 0 | delete command.w; |
| 124 | } | |
| 125 | ||
| 126 | // Execute command | |
| 127 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null); |
| 128 | }; | |
| 129 | ||
| 130 | 1 | DbCommand.createGetLastStatusCommand = DbCommand.createGetLastErrorCommand; |
| 131 | ||
| 132 | 1 | DbCommand.createGetPreviousErrorsCommand = function(db) { |
| 133 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getpreverror':1}, null); |
| 134 | }; | |
| 135 | ||
| 136 | 1 | DbCommand.createResetErrorHistoryCommand = function(db) { |
| 137 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reseterror':1}, null); |
| 138 | }; | |
| 139 | ||
| 140 | 1 | DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, options) { |
| 141 | 0 | var fieldHash = {}; |
| 142 | 0 | var indexes = []; |
| 143 | 0 | var keys; |
| 144 | ||
| 145 | // Get all the fields accordingly | |
| 146 | 0 | if('string' == typeof fieldOrSpec) { |
| 147 | // 'type' | |
| 148 | 0 | indexes.push(fieldOrSpec + '_' + 1); |
| 149 | 0 | fieldHash[fieldOrSpec] = 1; |
| 150 | ||
| 151 | 0 | } else if(utils.isArray(fieldOrSpec)) { |
| 152 | ||
| 153 | 0 | fieldOrSpec.forEach(function(f) { |
| 154 | 0 | if('string' == typeof f) { |
| 155 | // [{location:'2d'}, 'type'] | |
| 156 | 0 | indexes.push(f + '_' + 1); |
| 157 | 0 | fieldHash[f] = 1; |
| 158 | 0 | } else if(utils.isArray(f)) { |
| 159 | // [['location', '2d'],['type', 1]] | |
| 160 | 0 | indexes.push(f[0] + '_' + (f[1] || 1)); |
| 161 | 0 | fieldHash[f[0]] = f[1] || 1; |
| 162 | 0 | } else if(utils.isObject(f)) { |
| 163 | // [{location:'2d'}, {type:1}] | |
| 164 | 0 | keys = Object.keys(f); |
| 165 | 0 | keys.forEach(function(k) { |
| 166 | 0 | indexes.push(k + '_' + f[k]); |
| 167 | 0 | fieldHash[k] = f[k]; |
| 168 | }); | |
| 169 | } else { | |
| 170 | // undefined (ignore) | |
| 171 | } | |
| 172 | }); | |
| 173 | ||
| 174 | 0 | } else if(utils.isObject(fieldOrSpec)) { |
| 175 | // {location:'2d', type:1} | |
| 176 | 0 | keys = Object.keys(fieldOrSpec); |
| 177 | 0 | keys.forEach(function(key) { |
| 178 | 0 | indexes.push(key + '_' + fieldOrSpec[key]); |
| 179 | 0 | fieldHash[key] = fieldOrSpec[key]; |
| 180 | }); | |
| 181 | } | |
| 182 | ||
| 183 | // Generate the index name | |
| 184 | 0 | var indexName = typeof options.name == 'string' |
| 185 | ? options.name | |
| 186 | : indexes.join("_"); | |
| 187 | ||
| 188 | 0 | var selector = { |
| 189 | 'ns': db.databaseName + "." + collectionName, | |
| 190 | 'key': fieldHash, | |
| 191 | 'name': indexName | |
| 192 | } | |
| 193 | ||
| 194 | // Ensure we have a correct finalUnique | |
| 195 | 0 | var finalUnique = options == null || 'object' === typeof options |
| 196 | ? false | |
| 197 | : options; | |
| 198 | ||
| 199 | // Set up options | |
| 200 | 0 | options = options == null || typeof options == 'boolean' |
| 201 | ? {} | |
| 202 | : options; | |
| 203 | ||
| 204 | // Add all the options | |
| 205 | 0 | var keys = Object.keys(options); |
| 206 | 0 | for(var i = 0; i < keys.length; i++) { |
| 207 | 0 | selector[keys[i]] = options[keys[i]]; |
| 208 | } | |
| 209 | ||
| 210 | 0 | if(selector['unique'] == null) |
| 211 | 0 | selector['unique'] = finalUnique; |
| 212 | ||
| 213 | 0 | var name = db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION; |
| 214 | 0 | var cmd = new InsertCommand(db, name, false); |
| 215 | 0 | return cmd.add(selector); |
| 216 | }; | |
| 217 | ||
| 218 | 1 | DbCommand.logoutCommand = function(db, command_hash, options) { |
| 219 | 0 | var dbName = options != null && options['authdb'] != null ? options['authdb'] : db.databaseName; |
| 220 | 0 | return new DbCommand(db, dbName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); |
| 221 | } | |
| 222 | ||
| 223 | 1 | DbCommand.createDropIndexCommand = function(db, collectionName, indexName) { |
| 224 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'deleteIndexes':collectionName, 'index':indexName}, null); |
| 225 | }; | |
| 226 | ||
| 227 | 1 | DbCommand.createReIndexCommand = function(db, collectionName) { |
| 228 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reIndex':collectionName}, null); |
| 229 | }; | |
| 230 | ||
| 231 | 1 | DbCommand.createDropDatabaseCommand = function(db) { |
| 232 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'dropDatabase':1}, null); |
| 233 | }; | |
| 234 | ||
| 235 | 1 | DbCommand.createDbCommand = function(db, command_hash, options, auth_db) { |
| 236 | 0 | var db_name = (auth_db ? auth_db : db.databaseName) + "." + DbCommand.SYSTEM_COMMAND_COLLECTION; |
| 237 | 0 | options = options == null ? {checkKeys: false} : options; |
| 238 | 0 | return new DbCommand(db, db_name, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null, options); |
| 239 | }; | |
| 240 | ||
| 241 | 1 | DbCommand.createAdminDbCommand = function(db, command_hash) { |
| 242 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); |
| 243 | }; | |
| 244 | ||
| 245 | 1 | DbCommand.createAdminDbCommandSlaveOk = function(db, command_hash) { |
| 246 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null); |
| 247 | }; | |
| 248 | ||
| 249 | 1 | DbCommand.createDbSlaveOkCommand = function(db, command_hash, options) { |
| 250 | 0 | options = options == null ? {checkKeys: false} : options; |
| 251 | 0 | var dbName = options.dbName ? options.dbName : db.databaseName; |
| 252 | 0 | return new DbCommand(db, dbName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null, options); |
| 253 | }; | |
| 254 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var DeleteCommand = exports.DeleteCommand = function(db, collectionName, selector, flags) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | // Validate correctness off the selector | |
| 11 | 0 | var object = selector; |
| 12 | 0 | if(Buffer.isBuffer(object)) { |
| 13 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 14 | 0 | if(object_size != object.length) { |
| 15 | 0 | var error = new Error("delete raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 16 | 0 | error.name = 'MongoError'; |
| 17 | 0 | throw error; |
| 18 | } | |
| 19 | } | |
| 20 | ||
| 21 | 0 | this.flags = flags; |
| 22 | 0 | this.collectionName = collectionName; |
| 23 | 0 | this.selector = selector; |
| 24 | 0 | this.db = db; |
| 25 | }; | |
| 26 | ||
| 27 | 1 | inherits(DeleteCommand, BaseCommand); |
| 28 | ||
| 29 | 1 | DeleteCommand.OP_DELETE = 2006; |
| 30 | ||
| 31 | /* | |
| 32 | struct { | |
| 33 | MsgHeader header; // standard message header | |
| 34 | int32 ZERO; // 0 - reserved for future use | |
| 35 | cstring fullCollectionName; // "dbname.collectionname" | |
| 36 | int32 ZERO; // 0 - reserved for future use | |
| 37 | mongo.BSON selector; // query object. See below for details. | |
| 38 | } | |
| 39 | */ | |
| 40 | 1 | DeleteCommand.prototype.toBinary = function(bsonSettings) { |
| 41 | // Validate that we are not passing 0x00 in the colletion name | |
| 42 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 43 | 0 | throw new Error("namespace cannot contain a null character"); |
| 44 | } | |
| 45 | ||
| 46 | // Calculate total length of the document | |
| 47 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.selector, false, true) + (4 * 4); |
| 48 | ||
| 49 | // Enforce maximum bson size | |
| 50 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 51 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 52 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 53 | ||
| 54 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 55 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 56 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 57 | ||
| 58 | // Let's build the single pass buffer command | |
| 59 | 0 | var _index = 0; |
| 60 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 61 | // Write the header information to the buffer | |
| 62 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 63 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 64 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 65 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 66 | // Adjust index | |
| 67 | 0 | _index = _index + 4; |
| 68 | // Write the request ID | |
| 69 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 70 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 71 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 72 | 0 | _command[_index] = this.requestId & 0xff; |
| 73 | // Adjust index | |
| 74 | 0 | _index = _index + 4; |
| 75 | // Write zero | |
| 76 | 0 | _command[_index++] = 0; |
| 77 | 0 | _command[_index++] = 0; |
| 78 | 0 | _command[_index++] = 0; |
| 79 | 0 | _command[_index++] = 0; |
| 80 | // Write the op_code for the command | |
| 81 | 0 | _command[_index + 3] = (DeleteCommand.OP_DELETE >> 24) & 0xff; |
| 82 | 0 | _command[_index + 2] = (DeleteCommand.OP_DELETE >> 16) & 0xff; |
| 83 | 0 | _command[_index + 1] = (DeleteCommand.OP_DELETE >> 8) & 0xff; |
| 84 | 0 | _command[_index] = DeleteCommand.OP_DELETE & 0xff; |
| 85 | // Adjust index | |
| 86 | 0 | _index = _index + 4; |
| 87 | ||
| 88 | // Write zero | |
| 89 | 0 | _command[_index++] = 0; |
| 90 | 0 | _command[_index++] = 0; |
| 91 | 0 | _command[_index++] = 0; |
| 92 | 0 | _command[_index++] = 0; |
| 93 | ||
| 94 | // Write the collection name to the command | |
| 95 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 96 | 0 | _command[_index - 1] = 0; |
| 97 | ||
| 98 | // Write the flags | |
| 99 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 100 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 101 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 102 | 0 | _command[_index] = this.flags & 0xff; |
| 103 | // Adjust index | |
| 104 | 0 | _index = _index + 4; |
| 105 | ||
| 106 | // Document binary length | |
| 107 | 0 | var documentLength = 0 |
| 108 | ||
| 109 | // Serialize the selector | |
| 110 | // If we are passing a raw buffer, do minimal validation | |
| 111 | 0 | if(Buffer.isBuffer(this.selector)) { |
| 112 | 0 | documentLength = this.selector.length; |
| 113 | // Copy the data into the current buffer | |
| 114 | 0 | this.selector.copy(_command, _index); |
| 115 | } else { | |
| 116 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(this.selector, false, _command, _index) - _index + 1; |
| 117 | } | |
| 118 | ||
| 119 | // Write the length to the document | |
| 120 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 121 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 122 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 123 | 0 | _command[_index] = documentLength & 0xff; |
| 124 | // Update index in buffer | |
| 125 | 0 | _index = _index + documentLength; |
| 126 | // Add terminating 0 for the object | |
| 127 | 0 | _command[_index - 1] = 0; |
| 128 | 0 | return _command; |
| 129 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits, | |
| 3 | binaryutils = require('../utils'); | |
| 4 | ||
| 5 | /** | |
| 6 | Get More Document Command | |
| 7 | **/ | |
| 8 | 1 | var GetMoreCommand = exports.GetMoreCommand = function(db, collectionName, numberToReturn, cursorId) { |
| 9 | 0 | BaseCommand.call(this); |
| 10 | ||
| 11 | 0 | this.collectionName = collectionName; |
| 12 | 0 | this.numberToReturn = numberToReturn; |
| 13 | 0 | this.cursorId = cursorId; |
| 14 | 0 | this.db = db; |
| 15 | }; | |
| 16 | ||
| 17 | 1 | inherits(GetMoreCommand, BaseCommand); |
| 18 | ||
| 19 | 1 | GetMoreCommand.OP_GET_MORE = 2005; |
| 20 | ||
| 21 | 1 | GetMoreCommand.prototype.toBinary = function() { |
| 22 | // Validate that we are not passing 0x00 in the colletion name | |
| 23 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 24 | 0 | throw new Error("namespace cannot contain a null character"); |
| 25 | } | |
| 26 | ||
| 27 | // Calculate total length of the document | |
| 28 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 8 + (4 * 4); |
| 29 | // Let's build the single pass buffer command | |
| 30 | 0 | var _index = 0; |
| 31 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 32 | // Write the header information to the buffer | |
| 33 | 0 | _command[_index++] = totalLengthOfCommand & 0xff; |
| 34 | 0 | _command[_index++] = (totalLengthOfCommand >> 8) & 0xff; |
| 35 | 0 | _command[_index++] = (totalLengthOfCommand >> 16) & 0xff; |
| 36 | 0 | _command[_index++] = (totalLengthOfCommand >> 24) & 0xff; |
| 37 | ||
| 38 | // Write the request ID | |
| 39 | 0 | _command[_index++] = this.requestId & 0xff; |
| 40 | 0 | _command[_index++] = (this.requestId >> 8) & 0xff; |
| 41 | 0 | _command[_index++] = (this.requestId >> 16) & 0xff; |
| 42 | 0 | _command[_index++] = (this.requestId >> 24) & 0xff; |
| 43 | ||
| 44 | // Write zero | |
| 45 | 0 | _command[_index++] = 0; |
| 46 | 0 | _command[_index++] = 0; |
| 47 | 0 | _command[_index++] = 0; |
| 48 | 0 | _command[_index++] = 0; |
| 49 | ||
| 50 | // Write the op_code for the command | |
| 51 | 0 | _command[_index++] = GetMoreCommand.OP_GET_MORE & 0xff; |
| 52 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 8) & 0xff; |
| 53 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 16) & 0xff; |
| 54 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 24) & 0xff; |
| 55 | ||
| 56 | // Write zero | |
| 57 | 0 | _command[_index++] = 0; |
| 58 | 0 | _command[_index++] = 0; |
| 59 | 0 | _command[_index++] = 0; |
| 60 | 0 | _command[_index++] = 0; |
| 61 | ||
| 62 | // Write the collection name to the command | |
| 63 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 64 | 0 | _command[_index - 1] = 0; |
| 65 | ||
| 66 | // Number of documents to return | |
| 67 | 0 | _command[_index++] = this.numberToReturn & 0xff; |
| 68 | 0 | _command[_index++] = (this.numberToReturn >> 8) & 0xff; |
| 69 | 0 | _command[_index++] = (this.numberToReturn >> 16) & 0xff; |
| 70 | 0 | _command[_index++] = (this.numberToReturn >> 24) & 0xff; |
| 71 | ||
| 72 | // Encode the cursor id | |
| 73 | 0 | var low_bits = this.cursorId.getLowBits(); |
| 74 | // Encode low bits | |
| 75 | 0 | _command[_index++] = low_bits & 0xff; |
| 76 | 0 | _command[_index++] = (low_bits >> 8) & 0xff; |
| 77 | 0 | _command[_index++] = (low_bits >> 16) & 0xff; |
| 78 | 0 | _command[_index++] = (low_bits >> 24) & 0xff; |
| 79 | ||
| 80 | 0 | var high_bits = this.cursorId.getHighBits(); |
| 81 | // Encode high bits | |
| 82 | 0 | _command[_index++] = high_bits & 0xff; |
| 83 | 0 | _command[_index++] = (high_bits >> 8) & 0xff; |
| 84 | 0 | _command[_index++] = (high_bits >> 16) & 0xff; |
| 85 | 0 | _command[_index++] = (high_bits >> 24) & 0xff; |
| 86 | // Return command | |
| 87 | 0 | return _command; |
| 88 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var InsertCommand = exports.InsertCommand = function(db, collectionName, checkKeys, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | 0 | this.collectionName = collectionName; |
| 11 | 0 | this.documents = []; |
| 12 | 0 | this.checkKeys = checkKeys == null ? true : checkKeys; |
| 13 | 0 | this.db = db; |
| 14 | 0 | this.flags = 0; |
| 15 | 0 | this.serializeFunctions = false; |
| 16 | ||
| 17 | // Ensure valid options hash | |
| 18 | 0 | options = options == null ? {} : options; |
| 19 | ||
| 20 | // Check if we have keepGoing set -> set flag if it's the case | |
| 21 | 0 | if(options['keepGoing'] != null && options['keepGoing']) { |
| 22 | // This will finish inserting all non-index violating documents even if it returns an error | |
| 23 | 0 | this.flags = 1; |
| 24 | } | |
| 25 | ||
| 26 | // Check if we have keepGoing set -> set flag if it's the case | |
| 27 | 0 | if(options['continueOnError'] != null && options['continueOnError']) { |
| 28 | // This will finish inserting all non-index violating documents even if it returns an error | |
| 29 | 0 | this.flags = 1; |
| 30 | } | |
| 31 | ||
| 32 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 33 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 34 | 0 | this.serializeFunctions = true; |
| 35 | } | |
| 36 | }; | |
| 37 | ||
| 38 | 1 | inherits(InsertCommand, BaseCommand); |
| 39 | ||
| 40 | // OpCodes | |
| 41 | 1 | InsertCommand.OP_INSERT = 2002; |
| 42 | ||
| 43 | 1 | InsertCommand.prototype.add = function(document) { |
| 44 | 0 | if(Buffer.isBuffer(document)) { |
| 45 | 0 | var object_size = document[0] | document[1] << 8 | document[2] << 16 | document[3] << 24; |
| 46 | 0 | if(object_size != document.length) { |
| 47 | 0 | var error = new Error("insert raw message size does not match message header size [" + document.length + "] != [" + object_size + "]"); |
| 48 | 0 | error.name = 'MongoError'; |
| 49 | 0 | throw error; |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | 0 | this.documents.push(document); |
| 54 | 0 | return this; |
| 55 | }; | |
| 56 | ||
| 57 | /* | |
| 58 | struct { | |
| 59 | MsgHeader header; // standard message header | |
| 60 | int32 ZERO; // 0 - reserved for future use | |
| 61 | cstring fullCollectionName; // "dbname.collectionname" | |
| 62 | BSON[] documents; // one or more documents to insert into the collection | |
| 63 | } | |
| 64 | */ | |
| 65 | 1 | InsertCommand.prototype.toBinary = function(bsonSettings) { |
| 66 | // Validate that we are not passing 0x00 in the colletion name | |
| 67 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 68 | 0 | throw new Error("namespace cannot contain a null character"); |
| 69 | } | |
| 70 | ||
| 71 | // Calculate total length of the document | |
| 72 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + (4 * 4); |
| 73 | // var docLength = 0 | |
| 74 | 0 | for(var i = 0; i < this.documents.length; i++) { |
| 75 | 0 | if(Buffer.isBuffer(this.documents[i])) { |
| 76 | 0 | totalLengthOfCommand += this.documents[i].length; |
| 77 | } else { | |
| 78 | // Calculate size of document | |
| 79 | 0 | totalLengthOfCommand += this.db.bson.calculateObjectSize(this.documents[i], this.serializeFunctions, true); |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | // Enforce maximum bson size | |
| 84 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 85 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 86 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 87 | ||
| 88 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 89 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 90 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 91 | ||
| 92 | // Let's build the single pass buffer command | |
| 93 | 0 | var _index = 0; |
| 94 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 95 | // Write the header information to the buffer | |
| 96 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 97 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 98 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 99 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 100 | // Adjust index | |
| 101 | 0 | _index = _index + 4; |
| 102 | // Write the request ID | |
| 103 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 104 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 105 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 106 | 0 | _command[_index] = this.requestId & 0xff; |
| 107 | // Adjust index | |
| 108 | 0 | _index = _index + 4; |
| 109 | // Write zero | |
| 110 | 0 | _command[_index++] = 0; |
| 111 | 0 | _command[_index++] = 0; |
| 112 | 0 | _command[_index++] = 0; |
| 113 | 0 | _command[_index++] = 0; |
| 114 | // Write the op_code for the command | |
| 115 | 0 | _command[_index + 3] = (InsertCommand.OP_INSERT >> 24) & 0xff; |
| 116 | 0 | _command[_index + 2] = (InsertCommand.OP_INSERT >> 16) & 0xff; |
| 117 | 0 | _command[_index + 1] = (InsertCommand.OP_INSERT >> 8) & 0xff; |
| 118 | 0 | _command[_index] = InsertCommand.OP_INSERT & 0xff; |
| 119 | // Adjust index | |
| 120 | 0 | _index = _index + 4; |
| 121 | // Write flags if any | |
| 122 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 123 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 124 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 125 | 0 | _command[_index] = this.flags & 0xff; |
| 126 | // Adjust index | |
| 127 | 0 | _index = _index + 4; |
| 128 | // Write the collection name to the command | |
| 129 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 130 | 0 | _command[_index - 1] = 0; |
| 131 | ||
| 132 | // Write all the bson documents to the buffer at the index offset | |
| 133 | 0 | for(var i = 0; i < this.documents.length; i++) { |
| 134 | // Document binary length | |
| 135 | 0 | var documentLength = 0 |
| 136 | 0 | var object = this.documents[i]; |
| 137 | ||
| 138 | // Serialize the selector | |
| 139 | // If we are passing a raw buffer, do minimal validation | |
| 140 | 0 | if(Buffer.isBuffer(object)) { |
| 141 | 0 | documentLength = object.length; |
| 142 | // Copy the data into the current buffer | |
| 143 | 0 | object.copy(_command, _index); |
| 144 | } else { | |
| 145 | // Serialize the document straight to the buffer | |
| 146 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 147 | } | |
| 148 | ||
| 149 | // Write the length to the document | |
| 150 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 151 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 152 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 153 | 0 | _command[_index] = documentLength & 0xff; |
| 154 | // Update index in buffer | |
| 155 | 0 | _index = _index + documentLength; |
| 156 | // Add terminating 0 for the object | |
| 157 | 0 | _command[_index - 1] = 0; |
| 158 | } | |
| 159 | ||
| 160 | 0 | return _command; |
| 161 | }; | |
| 162 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits, | |
| 3 | binaryutils = require('../utils'); | |
| 4 | ||
| 5 | /** | |
| 6 | Insert Document Command | |
| 7 | **/ | |
| 8 | 1 | var KillCursorCommand = exports.KillCursorCommand = function(db, cursorIds) { |
| 9 | 0 | BaseCommand.call(this); |
| 10 | ||
| 11 | 0 | this.cursorIds = cursorIds; |
| 12 | 0 | this.db = db; |
| 13 | }; | |
| 14 | ||
| 15 | 1 | inherits(KillCursorCommand, BaseCommand); |
| 16 | ||
| 17 | 1 | KillCursorCommand.OP_KILL_CURSORS = 2007; |
| 18 | ||
| 19 | /* | |
| 20 | struct { | |
| 21 | MsgHeader header; // standard message header | |
| 22 | int32 ZERO; // 0 - reserved for future use | |
| 23 | int32 numberOfCursorIDs; // number of cursorIDs in message | |
| 24 | int64[] cursorIDs; // array of cursorIDs to close | |
| 25 | } | |
| 26 | */ | |
| 27 | 1 | KillCursorCommand.prototype.toBinary = function() { |
| 28 | // Calculate total length of the document | |
| 29 | 0 | var totalLengthOfCommand = 4 + 4 + (4 * 4) + (this.cursorIds.length * 8); |
| 30 | // Let's build the single pass buffer command | |
| 31 | 0 | var _index = 0; |
| 32 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 33 | // Write the header information to the buffer | |
| 34 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 35 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 36 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 37 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 38 | // Adjust index | |
| 39 | 0 | _index = _index + 4; |
| 40 | // Write the request ID | |
| 41 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 42 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 43 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 44 | 0 | _command[_index] = this.requestId & 0xff; |
| 45 | // Adjust index | |
| 46 | 0 | _index = _index + 4; |
| 47 | // Write zero | |
| 48 | 0 | _command[_index++] = 0; |
| 49 | 0 | _command[_index++] = 0; |
| 50 | 0 | _command[_index++] = 0; |
| 51 | 0 | _command[_index++] = 0; |
| 52 | // Write the op_code for the command | |
| 53 | 0 | _command[_index + 3] = (KillCursorCommand.OP_KILL_CURSORS >> 24) & 0xff; |
| 54 | 0 | _command[_index + 2] = (KillCursorCommand.OP_KILL_CURSORS >> 16) & 0xff; |
| 55 | 0 | _command[_index + 1] = (KillCursorCommand.OP_KILL_CURSORS >> 8) & 0xff; |
| 56 | 0 | _command[_index] = KillCursorCommand.OP_KILL_CURSORS & 0xff; |
| 57 | // Adjust index | |
| 58 | 0 | _index = _index + 4; |
| 59 | ||
| 60 | // Write zero | |
| 61 | 0 | _command[_index++] = 0; |
| 62 | 0 | _command[_index++] = 0; |
| 63 | 0 | _command[_index++] = 0; |
| 64 | 0 | _command[_index++] = 0; |
| 65 | ||
| 66 | // Number of cursors to kill | |
| 67 | 0 | var numberOfCursors = this.cursorIds.length; |
| 68 | 0 | _command[_index + 3] = (numberOfCursors >> 24) & 0xff; |
| 69 | 0 | _command[_index + 2] = (numberOfCursors >> 16) & 0xff; |
| 70 | 0 | _command[_index + 1] = (numberOfCursors >> 8) & 0xff; |
| 71 | 0 | _command[_index] = numberOfCursors & 0xff; |
| 72 | // Adjust index | |
| 73 | 0 | _index = _index + 4; |
| 74 | ||
| 75 | // Encode all the cursors | |
| 76 | 0 | for(var i = 0; i < this.cursorIds.length; i++) { |
| 77 | // Encode the cursor id | |
| 78 | 0 | var low_bits = this.cursorIds[i].getLowBits(); |
| 79 | // Encode low bits | |
| 80 | 0 | _command[_index + 3] = (low_bits >> 24) & 0xff; |
| 81 | 0 | _command[_index + 2] = (low_bits >> 16) & 0xff; |
| 82 | 0 | _command[_index + 1] = (low_bits >> 8) & 0xff; |
| 83 | 0 | _command[_index] = low_bits & 0xff; |
| 84 | // Adjust index | |
| 85 | 0 | _index = _index + 4; |
| 86 | ||
| 87 | 0 | var high_bits = this.cursorIds[i].getHighBits(); |
| 88 | // Encode high bits | |
| 89 | 0 | _command[_index + 3] = (high_bits >> 24) & 0xff; |
| 90 | 0 | _command[_index + 2] = (high_bits >> 16) & 0xff; |
| 91 | 0 | _command[_index + 1] = (high_bits >> 8) & 0xff; |
| 92 | 0 | _command[_index] = high_bits & 0xff; |
| 93 | // Adjust index | |
| 94 | 0 | _index = _index + 4; |
| 95 | } | |
| 96 | ||
| 97 | 0 | return _command; |
| 98 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var QueryCommand = exports.QueryCommand = function(db, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | // Validate correctness off the selector | |
| 11 | 0 | var object = query, |
| 12 | object_size; | |
| 13 | 0 | if(Buffer.isBuffer(object)) { |
| 14 | 0 | object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 15 | 0 | if(object_size != object.length) { |
| 16 | 0 | var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 17 | 0 | error.name = 'MongoError'; |
| 18 | 0 | throw error; |
| 19 | } | |
| 20 | } | |
| 21 | ||
| 22 | 0 | object = returnFieldSelector; |
| 23 | 0 | if(Buffer.isBuffer(object)) { |
| 24 | 0 | object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 25 | 0 | if(object_size != object.length) { |
| 26 | 0 | var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 27 | 0 | error.name = 'MongoError'; |
| 28 | 0 | throw error; |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | // Make sure we don't get a null exception | |
| 33 | 0 | options = options == null ? {} : options; |
| 34 | // Set up options | |
| 35 | 0 | this.collectionName = collectionName; |
| 36 | 0 | this.queryOptions = queryOptions; |
| 37 | 0 | this.numberToSkip = numberToSkip; |
| 38 | 0 | this.numberToReturn = numberToReturn; |
| 39 | ||
| 40 | // Ensure we have no null query | |
| 41 | 0 | query = query == null ? {} : query; |
| 42 | // Wrap query in the $query parameter so we can add read preferences for mongos | |
| 43 | 0 | this.query = query; |
| 44 | 0 | this.returnFieldSelector = returnFieldSelector; |
| 45 | 0 | this.db = db; |
| 46 | ||
| 47 | // Force the slave ok flag to be set if we are not using primary read preference | |
| 48 | 0 | if(this.db && this.db.slaveOk) { |
| 49 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 50 | } | |
| 51 | ||
| 52 | // If checkKeys set | |
| 53 | 0 | this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false; |
| 54 | ||
| 55 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 56 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 57 | 0 | this.serializeFunctions = true; |
| 58 | } | |
| 59 | }; | |
| 60 | ||
| 61 | 1 | inherits(QueryCommand, BaseCommand); |
| 62 | ||
| 63 | 1 | QueryCommand.OP_QUERY = 2004; |
| 64 | ||
| 65 | /* | |
| 66 | * Adds the read prefrence to the current command | |
| 67 | */ | |
| 68 | 1 | QueryCommand.prototype.setMongosReadPreference = function(readPreference, tags) { |
| 69 | // If we have readPreference set to true set to secondary prefered | |
| 70 | 0 | if(readPreference == true) { |
| 71 | 0 | readPreference = 'secondaryPreferred'; |
| 72 | 0 | } else if(readPreference == 'false') { |
| 73 | 0 | readPreference = 'primary'; |
| 74 | } | |
| 75 | ||
| 76 | // Force the slave ok flag to be set if we are not using primary read preference | |
| 77 | 0 | if(readPreference != false && readPreference != 'primary') { |
| 78 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 79 | } | |
| 80 | ||
| 81 | // Backward compatibility, ensure $query only set on read preference so 1.8.X works | |
| 82 | 0 | if((readPreference != null || tags != null) && this.query['$query'] == null) { |
| 83 | 0 | this.query = {'$query': this.query}; |
| 84 | } | |
| 85 | ||
| 86 | // If we have no readPreference set and no tags, check if the slaveOk bit is set | |
| 87 | 0 | if(readPreference == null && tags == null) { |
| 88 | // If we have a slaveOk bit set the read preference for MongoS | |
| 89 | 0 | if(this.queryOptions & QueryCommand.OPTS_SLAVE) { |
| 90 | 0 | this.query['$readPreference'] = {mode: 'secondary'} |
| 91 | } else { | |
| 92 | 0 | this.query['$readPreference'] = {mode: 'primary'} |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | // Build read preference object | |
| 97 | 0 | if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') { |
| 98 | 0 | this.query['$readPreference'] = readPreference.toObject(); |
| 99 | 0 | } else if(readPreference != null) { |
| 100 | // Add the read preference | |
| 101 | 0 | this.query['$readPreference'] = {mode: readPreference}; |
| 102 | ||
| 103 | // If we have tags let's add them | |
| 104 | 0 | if(tags != null) { |
| 105 | 0 | this.query['$readPreference']['tags'] = tags; |
| 106 | } | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | /* | |
| 111 | struct { | |
| 112 | MsgHeader header; // standard message header | |
| 113 | int32 opts; // query options. See below for details. | |
| 114 | cstring fullCollectionName; // "dbname.collectionname" | |
| 115 | int32 numberToSkip; // number of documents to skip when returning results | |
| 116 | int32 numberToReturn; // number of documents to return in the first OP_REPLY | |
| 117 | BSON query ; // query object. See below for details. | |
| 118 | [ BSON returnFieldSelector; ] // OPTIONAL : selector indicating the fields to return. See below for details. | |
| 119 | } | |
| 120 | */ | |
| 121 | 1 | QueryCommand.prototype.toBinary = function(bsonSettings) { |
| 122 | // Validate that we are not passing 0x00 in the colletion name | |
| 123 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 124 | 0 | throw new Error("namespace cannot contain a null character"); |
| 125 | } | |
| 126 | ||
| 127 | // Total length of the command | |
| 128 | 0 | var totalLengthOfCommand = 0; |
| 129 | // Calculate total length of the document | |
| 130 | 0 | if(Buffer.isBuffer(this.query)) { |
| 131 | 0 | totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.query.length + (4 * 4); |
| 132 | } else { | |
| 133 | 0 | totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.db.bson.calculateObjectSize(this.query, this.serializeFunctions, true) + (4 * 4); |
| 134 | } | |
| 135 | ||
| 136 | // Calculate extra fields size | |
| 137 | 0 | if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { |
| 138 | 0 | if(Object.keys(this.returnFieldSelector).length > 0) { |
| 139 | 0 | totalLengthOfCommand += this.db.bson.calculateObjectSize(this.returnFieldSelector, this.serializeFunctions, true); |
| 140 | } | |
| 141 | 0 | } else if(Buffer.isBuffer(this.returnFieldSelector)) { |
| 142 | 0 | totalLengthOfCommand += this.returnFieldSelector.length; |
| 143 | } | |
| 144 | ||
| 145 | // Enforce maximum bson size | |
| 146 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 147 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 148 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 149 | ||
| 150 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 151 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 152 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 153 | ||
| 154 | // Let's build the single pass buffer command | |
| 155 | 0 | var _index = 0; |
| 156 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 157 | // Write the header information to the buffer | |
| 158 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 159 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 160 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 161 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 162 | // Adjust index | |
| 163 | 0 | _index = _index + 4; |
| 164 | // Write the request ID | |
| 165 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 166 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 167 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 168 | 0 | _command[_index] = this.requestId & 0xff; |
| 169 | // Adjust index | |
| 170 | 0 | _index = _index + 4; |
| 171 | // Write zero | |
| 172 | 0 | _command[_index++] = 0; |
| 173 | 0 | _command[_index++] = 0; |
| 174 | 0 | _command[_index++] = 0; |
| 175 | 0 | _command[_index++] = 0; |
| 176 | // Write the op_code for the command | |
| 177 | 0 | _command[_index + 3] = (QueryCommand.OP_QUERY >> 24) & 0xff; |
| 178 | 0 | _command[_index + 2] = (QueryCommand.OP_QUERY >> 16) & 0xff; |
| 179 | 0 | _command[_index + 1] = (QueryCommand.OP_QUERY >> 8) & 0xff; |
| 180 | 0 | _command[_index] = QueryCommand.OP_QUERY & 0xff; |
| 181 | // Adjust index | |
| 182 | 0 | _index = _index + 4; |
| 183 | ||
| 184 | // Write the query options | |
| 185 | 0 | _command[_index + 3] = (this.queryOptions >> 24) & 0xff; |
| 186 | 0 | _command[_index + 2] = (this.queryOptions >> 16) & 0xff; |
| 187 | 0 | _command[_index + 1] = (this.queryOptions >> 8) & 0xff; |
| 188 | 0 | _command[_index] = this.queryOptions & 0xff; |
| 189 | // Adjust index | |
| 190 | 0 | _index = _index + 4; |
| 191 | ||
| 192 | // Write the collection name to the command | |
| 193 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 194 | 0 | _command[_index - 1] = 0; |
| 195 | ||
| 196 | // Write the number of documents to skip | |
| 197 | 0 | _command[_index + 3] = (this.numberToSkip >> 24) & 0xff; |
| 198 | 0 | _command[_index + 2] = (this.numberToSkip >> 16) & 0xff; |
| 199 | 0 | _command[_index + 1] = (this.numberToSkip >> 8) & 0xff; |
| 200 | 0 | _command[_index] = this.numberToSkip & 0xff; |
| 201 | // Adjust index | |
| 202 | 0 | _index = _index + 4; |
| 203 | ||
| 204 | // Write the number of documents to return | |
| 205 | 0 | _command[_index + 3] = (this.numberToReturn >> 24) & 0xff; |
| 206 | 0 | _command[_index + 2] = (this.numberToReturn >> 16) & 0xff; |
| 207 | 0 | _command[_index + 1] = (this.numberToReturn >> 8) & 0xff; |
| 208 | 0 | _command[_index] = this.numberToReturn & 0xff; |
| 209 | // Adjust index | |
| 210 | 0 | _index = _index + 4; |
| 211 | ||
| 212 | // Document binary length | |
| 213 | 0 | var documentLength = 0 |
| 214 | 0 | var object = this.query; |
| 215 | ||
| 216 | // Serialize the selector | |
| 217 | 0 | if(Buffer.isBuffer(object)) { |
| 218 | 0 | documentLength = object.length; |
| 219 | // Copy the data into the current buffer | |
| 220 | 0 | object.copy(_command, _index); |
| 221 | } else { | |
| 222 | // Serialize the document straight to the buffer | |
| 223 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 224 | } | |
| 225 | ||
| 226 | // Write the length to the document | |
| 227 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 228 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 229 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 230 | 0 | _command[_index] = documentLength & 0xff; |
| 231 | // Update index in buffer | |
| 232 | 0 | _index = _index + documentLength; |
| 233 | // Add terminating 0 for the object | |
| 234 | 0 | _command[_index - 1] = 0; |
| 235 | ||
| 236 | // Push field selector if available | |
| 237 | 0 | if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { |
| 238 | 0 | if(Object.keys(this.returnFieldSelector).length > 0) { |
| 239 | 0 | var documentLength = this.db.bson.serializeWithBufferAndIndex(this.returnFieldSelector, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 240 | // Write the length to the document | |
| 241 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 242 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 243 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 244 | 0 | _command[_index] = documentLength & 0xff; |
| 245 | // Update index in buffer | |
| 246 | 0 | _index = _index + documentLength; |
| 247 | // Add terminating 0 for the object | |
| 248 | 0 | _command[_index - 1] = 0; |
| 249 | } | |
| 250 | 0 | } if(this.returnFieldSelector != null && Buffer.isBuffer(this.returnFieldSelector)) { |
| 251 | // Document binary length | |
| 252 | 0 | var documentLength = 0 |
| 253 | 0 | var object = this.returnFieldSelector; |
| 254 | ||
| 255 | // Serialize the selector | |
| 256 | 0 | documentLength = object.length; |
| 257 | // Copy the data into the current buffer | |
| 258 | 0 | object.copy(_command, _index); |
| 259 | ||
| 260 | // Write the length to the document | |
| 261 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 262 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 263 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 264 | 0 | _command[_index] = documentLength & 0xff; |
| 265 | // Update index in buffer | |
| 266 | 0 | _index = _index + documentLength; |
| 267 | // Add terminating 0 for the object | |
| 268 | 0 | _command[_index - 1] = 0; |
| 269 | } | |
| 270 | ||
| 271 | // Return finished command | |
| 272 | 0 | return _command; |
| 273 | }; | |
| 274 | ||
| 275 | // Constants | |
| 276 | 1 | QueryCommand.OPTS_NONE = 0; |
| 277 | 1 | QueryCommand.OPTS_TAILABLE_CURSOR = 2; |
| 278 | 1 | QueryCommand.OPTS_SLAVE = 4; |
| 279 | 1 | QueryCommand.OPTS_OPLOG_REPLAY = 8; |
| 280 | 1 | QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16; |
| 281 | 1 | QueryCommand.OPTS_AWAIT_DATA = 32; |
| 282 | 1 | QueryCommand.OPTS_EXHAUST = 64; |
| 283 | 1 | QueryCommand.OPTS_PARTIAL = 128; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Update Document Command | |
| 6 | **/ | |
| 7 | 1 | var UpdateCommand = exports.UpdateCommand = function(db, collectionName, spec, document, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | 0 | var object = spec; |
| 11 | 0 | if(Buffer.isBuffer(object)) { |
| 12 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 13 | 0 | if(object_size != object.length) { |
| 14 | 0 | var error = new Error("update spec raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 15 | 0 | error.name = 'MongoError'; |
| 16 | 0 | throw error; |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | 0 | var object = document; |
| 21 | 0 | if(Buffer.isBuffer(object)) { |
| 22 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 23 | 0 | if(object_size != object.length) { |
| 24 | 0 | var error = new Error("update document raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 25 | 0 | error.name = 'MongoError'; |
| 26 | 0 | throw error; |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | 0 | this.collectionName = collectionName; |
| 31 | 0 | this.spec = spec; |
| 32 | 0 | this.document = document; |
| 33 | 0 | this.db = db; |
| 34 | 0 | this.serializeFunctions = false; |
| 35 | 0 | this.checkKeys = typeof options.checkKeys != 'boolean' ? false : options.checkKeys; |
| 36 | ||
| 37 | // Generate correct flags | |
| 38 | 0 | var db_upsert = 0; |
| 39 | 0 | var db_multi_update = 0; |
| 40 | 0 | db_upsert = options != null && options['upsert'] != null ? (options['upsert'] == true ? 1 : 0) : db_upsert; |
| 41 | 0 | db_multi_update = options != null && options['multi'] != null ? (options['multi'] == true ? 1 : 0) : db_multi_update; |
| 42 | ||
| 43 | // Flags | |
| 44 | 0 | this.flags = parseInt(db_multi_update.toString() + db_upsert.toString(), 2); |
| 45 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 46 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 47 | 0 | this.serializeFunctions = true; |
| 48 | } | |
| 49 | }; | |
| 50 | ||
| 51 | 1 | inherits(UpdateCommand, BaseCommand); |
| 52 | ||
| 53 | 1 | UpdateCommand.OP_UPDATE = 2001; |
| 54 | ||
| 55 | /* | |
| 56 | struct { | |
| 57 | MsgHeader header; // standard message header | |
| 58 | int32 ZERO; // 0 - reserved for future use | |
| 59 | cstring fullCollectionName; // "dbname.collectionname" | |
| 60 | int32 flags; // bit vector. see below | |
| 61 | BSON spec; // the query to select the document | |
| 62 | BSON document; // the document data to update with or insert | |
| 63 | } | |
| 64 | */ | |
| 65 | 1 | UpdateCommand.prototype.toBinary = function(bsonSettings) { |
| 66 | // Validate that we are not passing 0x00 in the colletion name | |
| 67 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 68 | 0 | throw new Error("namespace cannot contain a null character"); |
| 69 | } | |
| 70 | ||
| 71 | // Calculate total length of the document | |
| 72 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.spec, false, true) + |
| 73 | this.db.bson.calculateObjectSize(this.document, this.serializeFunctions, true) + (4 * 4); | |
| 74 | ||
| 75 | // Enforce maximum bson size | |
| 76 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 77 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 78 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 79 | ||
| 80 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 81 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 82 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 83 | ||
| 84 | // Let's build the single pass buffer command | |
| 85 | 0 | var _index = 0; |
| 86 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 87 | // Write the header information to the buffer | |
| 88 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 89 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 90 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 91 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 92 | // Adjust index | |
| 93 | 0 | _index = _index + 4; |
| 94 | // Write the request ID | |
| 95 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 96 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 97 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 98 | 0 | _command[_index] = this.requestId & 0xff; |
| 99 | // Adjust index | |
| 100 | 0 | _index = _index + 4; |
| 101 | // Write zero | |
| 102 | 0 | _command[_index++] = 0; |
| 103 | 0 | _command[_index++] = 0; |
| 104 | 0 | _command[_index++] = 0; |
| 105 | 0 | _command[_index++] = 0; |
| 106 | // Write the op_code for the command | |
| 107 | 0 | _command[_index + 3] = (UpdateCommand.OP_UPDATE >> 24) & 0xff; |
| 108 | 0 | _command[_index + 2] = (UpdateCommand.OP_UPDATE >> 16) & 0xff; |
| 109 | 0 | _command[_index + 1] = (UpdateCommand.OP_UPDATE >> 8) & 0xff; |
| 110 | 0 | _command[_index] = UpdateCommand.OP_UPDATE & 0xff; |
| 111 | // Adjust index | |
| 112 | 0 | _index = _index + 4; |
| 113 | ||
| 114 | // Write zero | |
| 115 | 0 | _command[_index++] = 0; |
| 116 | 0 | _command[_index++] = 0; |
| 117 | 0 | _command[_index++] = 0; |
| 118 | 0 | _command[_index++] = 0; |
| 119 | ||
| 120 | // Write the collection name to the command | |
| 121 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 122 | 0 | _command[_index - 1] = 0; |
| 123 | ||
| 124 | // Write the update flags | |
| 125 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 126 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 127 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 128 | 0 | _command[_index] = this.flags & 0xff; |
| 129 | // Adjust index | |
| 130 | 0 | _index = _index + 4; |
| 131 | ||
| 132 | // Document binary length | |
| 133 | 0 | var documentLength = 0 |
| 134 | 0 | var object = this.spec; |
| 135 | ||
| 136 | // Serialize the selector | |
| 137 | // If we are passing a raw buffer, do minimal validation | |
| 138 | 0 | if(Buffer.isBuffer(object)) { |
| 139 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 140 | 0 | if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 141 | 0 | documentLength = object.length; |
| 142 | // Copy the data into the current buffer | |
| 143 | 0 | object.copy(_command, _index); |
| 144 | } else { | |
| 145 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, false) - _index + 1; |
| 146 | } | |
| 147 | ||
| 148 | // Write the length to the document | |
| 149 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 150 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 151 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 152 | 0 | _command[_index] = documentLength & 0xff; |
| 153 | // Update index in buffer | |
| 154 | 0 | _index = _index + documentLength; |
| 155 | // Add terminating 0 for the object | |
| 156 | 0 | _command[_index - 1] = 0; |
| 157 | ||
| 158 | // Document binary length | |
| 159 | 0 | var documentLength = 0 |
| 160 | 0 | var object = this.document; |
| 161 | ||
| 162 | // Serialize the document | |
| 163 | // If we are passing a raw buffer, do minimal validation | |
| 164 | 0 | if(Buffer.isBuffer(object)) { |
| 165 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 166 | 0 | if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 167 | 0 | documentLength = object.length; |
| 168 | // Copy the data into the current buffer | |
| 169 | 0 | object.copy(_command, _index); |
| 170 | } else { | |
| 171 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, false, _command, _index, this.serializeFunctions) - _index + 1; |
| 172 | } | |
| 173 | ||
| 174 | // Write the length to the document | |
| 175 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 176 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 177 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 178 | 0 | _command[_index] = documentLength & 0xff; |
| 179 | // Update index in buffer | |
| 180 | 0 | _index = _index + documentLength; |
| 181 | // Add terminating 0 for the object | |
| 182 | 0 | _command[_index - 1] = 0; |
| 183 | ||
| 184 | 0 | return _command; |
| 185 | }; | |
| 186 | ||
| 187 | // Constants | |
| 188 | 1 | UpdateCommand.DB_UPSERT = 0; |
| 189 | 1 | UpdateCommand.DB_MULTI_UPDATE = 1; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var EventEmitter = require('events').EventEmitter |
| 2 | , inherits = require('util').inherits | |
| 3 | , utils = require('../utils') | |
| 4 | , mongodb_cr_authenticate = require('../auth/mongodb_cr.js').authenticate | |
| 5 | , mongodb_gssapi_authenticate = require('../auth/mongodb_gssapi.js').authenticate | |
| 6 | , mongodb_sspi_authenticate = require('../auth/mongodb_sspi.js').authenticate | |
| 7 | , mongodb_plain_authenticate = require('../auth/mongodb_plain.js').authenticate | |
| 8 | , mongodb_x509_authenticate = require('../auth/mongodb_x509.js').authenticate; | |
| 9 | ||
| 10 | 1 | var id = 0; |
| 11 | ||
| 12 | /** | |
| 13 | * Internal class for callback storage | |
| 14 | * @ignore | |
| 15 | */ | |
| 16 | 1 | var CallbackStore = function() { |
| 17 | // Make class an event emitter | |
| 18 | 0 | EventEmitter.call(this); |
| 19 | // Add a info about call variable | |
| 20 | 0 | this._notReplied = {}; |
| 21 | 0 | this.id = id++; |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | */ | |
| 27 | 1 | inherits(CallbackStore, EventEmitter); |
| 28 | ||
| 29 | 1 | CallbackStore.prototype.notRepliedToIds = function() { |
| 30 | 0 | return Object.keys(this._notReplied); |
| 31 | } | |
| 32 | ||
| 33 | 1 | CallbackStore.prototype.callbackInfo = function(id) { |
| 34 | 0 | return this._notReplied[id]; |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Internal class for holding non-executed commands | |
| 39 | * @ignore | |
| 40 | */ | |
| 41 | 1 | var NonExecutedOperationStore = function(config) { |
| 42 | 0 | var commands = { |
| 43 | read: [] | |
| 44 | , write_reads: [] | |
| 45 | , write: [] | |
| 46 | }; | |
| 47 | ||
| 48 | // Execute all callbacks | |
| 49 | 0 | var fireCallbacksWithError = function(error, commands) { |
| 50 | 0 | while(commands.length > 0) { |
| 51 | 0 | var command = commands.shift(); |
| 52 | 0 | if(typeof command.callback == 'function') { |
| 53 | 0 | command.callback(error); |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | 0 | this.count = function() { |
| 59 | 0 | return commands.read.length |
| 60 | + commands.write_reads.length | |
| 61 | + commands.write.length; | |
| 62 | } | |
| 63 | ||
| 64 | 0 | this.write = function(op) { |
| 65 | 0 | commands.write.push(op); |
| 66 | } | |
| 67 | ||
| 68 | 0 | this.read_from_writer = function(op) { |
| 69 | 0 | commands.write_reads.push(op); |
| 70 | } | |
| 71 | ||
| 72 | 0 | this.read = function(op) { |
| 73 | 0 | commands.read.push(op); |
| 74 | } | |
| 75 | ||
| 76 | 0 | this.validateBufferLimit = function(numberToFailOn) { |
| 77 | 0 | if(numberToFailOn == -1 || numberToFailOn == null) |
| 78 | 0 | return true; |
| 79 | ||
| 80 | // Error passed back | |
| 81 | 0 | var error = utils.toError("No connection operations buffering limit of " + numberToFailOn + " reached"); |
| 82 | ||
| 83 | // If we have passed the number of items to buffer we need to fail | |
| 84 | 0 | if(numberToFailOn < this.count()) { |
| 85 | // Fail all of the callbacks | |
| 86 | 0 | fireCallbacksWithError(error, commands.read); |
| 87 | 0 | fireCallbacksWithError(error, commands.write_reads); |
| 88 | 0 | fireCallbacksWithError(error, commands.write); |
| 89 | } | |
| 90 | ||
| 91 | // Return false | |
| 92 | 0 | return false; |
| 93 | } | |
| 94 | ||
| 95 | 0 | this.execute_queries = function(executeInsertCommand) { |
| 96 | 0 | var connection = config.checkoutReader(); |
| 97 | 0 | if(connection == null || connection instanceof Error) return; |
| 98 | ||
| 99 | // Write out all the queries | |
| 100 | 0 | while(commands.read.length > 0) { |
| 101 | // Get the next command | |
| 102 | 0 | var command = commands.read.shift(); |
| 103 | 0 | command.options.connection = connection; |
| 104 | // Execute the next command | |
| 105 | 0 | command.executeQueryCommand(command.db, command.db_command, command.options, command.callback); |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | 0 | this.execute_writes = function() { |
| 110 | 0 | var connection = config.checkoutWriter(); |
| 111 | 0 | if(connection == null || connection instanceof Error) return; |
| 112 | ||
| 113 | // Write out all the queries to the primary | |
| 114 | 0 | while(commands.write_reads.length > 0) { |
| 115 | // Get the next command | |
| 116 | 0 | var command = commands.write_reads.shift(); |
| 117 | 0 | command.options.connection = connection; |
| 118 | // Execute the next command | |
| 119 | 0 | command.executeQueryCommand(command.db, command.db_command, command.options, command.callback); |
| 120 | } | |
| 121 | ||
| 122 | // Execute all write operations | |
| 123 | 0 | while(commands.write.length > 0) { |
| 124 | // Get the next command | |
| 125 | 0 | var command = commands.write.shift(); |
| 126 | // Set the connection | |
| 127 | 0 | command.options.connection = connection; |
| 128 | // Execute the next command | |
| 129 | 0 | command.executeInsertCommand(command.db, command.db_command, command.options, command.callback); |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * Internal class for authentication storage | |
| 136 | * @ignore | |
| 137 | */ | |
| 138 | 1 | var AuthStore = function() { |
| 139 | 0 | var _auths = []; |
| 140 | ||
| 141 | 0 | this.add = function(authMechanism, dbName, username, password, authdbName, gssapiServiceName) { |
| 142 | // Check for duplicates | |
| 143 | 0 | if(!this.contains(dbName)) { |
| 144 | // Base config | |
| 145 | 0 | var config = { |
| 146 | 'username':username | |
| 147 | , 'password':password | |
| 148 | , 'db': dbName | |
| 149 | , 'authMechanism': authMechanism | |
| 150 | , 'gssapiServiceName': gssapiServiceName | |
| 151 | }; | |
| 152 | ||
| 153 | // Add auth source if passed in | |
| 154 | 0 | if(typeof authdbName == 'string') { |
| 155 | 0 | config['authdb'] = authdbName; |
| 156 | } | |
| 157 | ||
| 158 | // Push the config | |
| 159 | 0 | _auths.push(config); |
| 160 | } | |
| 161 | } | |
| 162 | ||
| 163 | 0 | this.contains = function(dbName) { |
| 164 | 0 | for(var i = 0; i < _auths.length; i++) { |
| 165 | 0 | if(_auths[i].db == dbName) return true; |
| 166 | } | |
| 167 | ||
| 168 | 0 | return false; |
| 169 | } | |
| 170 | ||
| 171 | 0 | this.remove = function(dbName) { |
| 172 | 0 | var newAuths = []; |
| 173 | ||
| 174 | // Filter out all the login details | |
| 175 | 0 | for(var i = 0; i < _auths.length; i++) { |
| 176 | 0 | if(_auths[i].db != dbName) newAuths.push(_auths[i]); |
| 177 | } | |
| 178 | ||
| 179 | // Set the filtered list | |
| 180 | 0 | _auths = newAuths; |
| 181 | } | |
| 182 | ||
| 183 | 0 | this.get = function(index) { |
| 184 | 0 | return _auths[index]; |
| 185 | } | |
| 186 | ||
| 187 | 0 | this.length = function() { |
| 188 | 0 | return _auths.length; |
| 189 | } | |
| 190 | ||
| 191 | 0 | this.toArray = function() { |
| 192 | 0 | return _auths.slice(0); |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | /** | |
| 197 | * Internal class for storing db references | |
| 198 | * @ignore | |
| 199 | */ | |
| 200 | 1 | var DbStore = function() { |
| 201 | 0 | var _dbs = []; |
| 202 | ||
| 203 | 0 | this.add = function(db) { |
| 204 | 0 | var found = false; |
| 205 | ||
| 206 | // Only add if it does not exist already | |
| 207 | 0 | for(var i = 0; i < _dbs.length; i++) { |
| 208 | 0 | if(db.databaseName == _dbs[i].databaseName) found = true; |
| 209 | } | |
| 210 | ||
| 211 | // Only add if it does not already exist | |
| 212 | 0 | if(!found) { |
| 213 | 0 | _dbs.push(db); |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | 0 | this.reset = function() { |
| 218 | 0 | _dbs = []; |
| 219 | } | |
| 220 | ||
| 221 | 0 | this.db = function() { |
| 222 | 0 | return _dbs; |
| 223 | } | |
| 224 | ||
| 225 | 0 | this.fetch = function(databaseName) { |
| 226 | // Only add if it does not exist already | |
| 227 | 0 | for(var i = 0; i < _dbs.length; i++) { |
| 228 | 0 | if(databaseName == _dbs[i].databaseName) |
| 229 | 0 | return _dbs[i]; |
| 230 | } | |
| 231 | ||
| 232 | 0 | return null; |
| 233 | } | |
| 234 | ||
| 235 | 0 | this.emit = function(event, message, object, reset, filterDb, rethrow_if_no_listeners) { |
| 236 | 0 | var emitted = false; |
| 237 | ||
| 238 | // Not emitted and we have enabled rethrow, let process.uncaughtException | |
| 239 | // deal with the issue | |
| 240 | 0 | if(!emitted && rethrow_if_no_listeners) { |
| 241 | 0 | return process.nextTick(function() { |
| 242 | 0 | throw message; |
| 243 | }) | |
| 244 | } | |
| 245 | ||
| 246 | // Emit the events | |
| 247 | 0 | for(var i = 0; i < _dbs.length; i++) { |
| 248 | 0 | if(_dbs[i].listeners(event).length > 0) { |
| 249 | 0 | if(filterDb == null || filterDb.databaseName !== _dbs[i].databaseName |
| 250 | || filterDb.tag !== _dbs[i].tag) { | |
| 251 | 0 | _dbs[i].emit(event, message, object == null ? _dbs[i] : object); |
| 252 | 0 | emitted = true; |
| 253 | } | |
| 254 | } | |
| 255 | } | |
| 256 | ||
| 257 | // Emit error message | |
| 258 | 0 | if(message |
| 259 | && event == 'error' | |
| 260 | && !emitted | |
| 261 | && rethrow_if_no_listeners | |
| 262 | && object && object.db) { | |
| 263 | 0 | process.nextTick(function() { |
| 264 | 0 | object.db.emit(event, message, null); |
| 265 | }) | |
| 266 | } | |
| 267 | } | |
| 268 | } | |
| 269 | ||
| 270 | 1 | var Base = function Base() { |
| 271 | 0 | EventEmitter.call(this); |
| 272 | ||
| 273 | // Callback store is part of connection specification | |
| 274 | 0 | if(Base._callBackStore == null) { |
| 275 | 0 | Base._callBackStore = new CallbackStore(); |
| 276 | } | |
| 277 | ||
| 278 | // Create a new callback store | |
| 279 | 0 | this._callBackStore = new CallbackStore(); |
| 280 | // All commands not being executed | |
| 281 | 0 | this._commandsStore = new NonExecutedOperationStore(this); |
| 282 | // Create a new auth store | |
| 283 | 0 | this.auth = new AuthStore(); |
| 284 | // Contains all the dbs attached to this server config | |
| 285 | 0 | this._dbStore = new DbStore(); |
| 286 | } | |
| 287 | ||
| 288 | /** | |
| 289 | * @ignore | |
| 290 | */ | |
| 291 | 1 | inherits(Base, EventEmitter); |
| 292 | ||
| 293 | /** | |
| 294 | * @ignore | |
| 295 | */ | |
| 296 | 1 | Base.prototype._apply_auths = function(db, callback) { |
| 297 | 0 | _apply_auths_serially(this, db, this.auth.toArray(), callback); |
| 298 | } | |
| 299 | ||
| 300 | 1 | var _apply_auths_serially = function(self, db, auths, callback) { |
| 301 | 0 | if(auths.length == 0) return callback(null, null); |
| 302 | // Get the first auth | |
| 303 | 0 | var auth = auths.shift(); |
| 304 | 0 | var connections = self.allRawConnections(); |
| 305 | 0 | var connectionsLeft = connections.length; |
| 306 | 0 | var options = {}; |
| 307 | ||
| 308 | 0 | if(auth.authMechanism == 'GSSAPI') { |
| 309 | // We have the kerberos library, execute auth process | |
| 310 | 0 | if(process.platform == 'win32') { |
| 311 | 0 | mongodb_sspi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 312 | } else { | |
| 313 | 0 | mongodb_gssapi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 314 | } | |
| 315 | 0 | } else if(auth.authMechanism == 'MONGODB-CR') { |
| 316 | 0 | mongodb_cr_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 317 | 0 | } else if(auth.authMechanism == 'PLAIN') { |
| 318 | 0 | mongodb_plain_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 319 | 0 | } else if(auth.authMechanism == 'MONGODB-X509') { |
| 320 | 0 | mongodb_x509_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 321 | } | |
| 322 | } | |
| 323 | ||
| 324 | /** | |
| 325 | * Fire all the errors | |
| 326 | * @ignore | |
| 327 | */ | |
| 328 | 1 | Base.prototype.__executeAllCallbacksWithError = function(err) { |
| 329 | // Check all callbacks | |
| 330 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 331 | // For each key check if it's a callback that needs to be returned | |
| 332 | 0 | for(var j = 0; j < keys.length; j++) { |
| 333 | 0 | var info = this._callBackStore._notReplied[keys[j]]; |
| 334 | // Execute callback with error | |
| 335 | 0 | this._callBackStore.emit(keys[j], err, null); |
| 336 | // Remove the key | |
| 337 | 0 | delete this._callBackStore._notReplied[keys[j]]; |
| 338 | // Force cleanup _events, node.js seems to set it as a null value | |
| 339 | 0 | if(this._callBackStore._events) { |
| 340 | 0 | delete this._callBackStore._events[keys[j]]; |
| 341 | } | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | /** | |
| 346 | * Fire all the errors | |
| 347 | * @ignore | |
| 348 | */ | |
| 349 | 1 | Base.prototype.__executeAllServerSpecificErrorCallbacks = function(host, port, err) { |
| 350 | // Check all callbacks | |
| 351 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 352 | // For each key check if it's a callback that needs to be returned | |
| 353 | 0 | for(var j = 0; j < keys.length; j++) { |
| 354 | 0 | var info = this._callBackStore._notReplied[keys[j]]; |
| 355 | ||
| 356 | 0 | if(info.connection) { |
| 357 | // Unpack the connection settings | |
| 358 | 0 | var _host = info.connection.socketOptions.host; |
| 359 | 0 | var _port = info.connection.socketOptions.port; |
| 360 | // If the server matches execute the callback with the error | |
| 361 | 0 | if(_port == port && _host == host) { |
| 362 | 0 | this._callBackStore.emit(keys[j], err, null); |
| 363 | // Remove the key | |
| 364 | 0 | delete this._callBackStore._notReplied[keys[j]]; |
| 365 | // Force cleanup _events, node.js seems to set it as a null value | |
| 366 | 0 | if(this._callBackStore._events) { |
| 367 | 0 | delete this._callBackStore._events[keys[j]]; |
| 368 | } | |
| 369 | } | |
| 370 | } | |
| 371 | } | |
| 372 | } | |
| 373 | ||
| 374 | /** | |
| 375 | * Register a handler | |
| 376 | * @ignore | |
| 377 | * @api private | |
| 378 | */ | |
| 379 | 1 | Base.prototype._registerHandler = function(db_command, raw, connection, exhaust, callback) { |
| 380 | // Check if we have exhausted | |
| 381 | 0 | if(typeof exhaust == 'function') { |
| 382 | 0 | callback = exhaust; |
| 383 | 0 | exhaust = false; |
| 384 | } | |
| 385 | ||
| 386 | // Add the callback to the list of handlers | |
| 387 | 0 | this._callBackStore.once(db_command.getRequestId(), callback); |
| 388 | // Add the information about the reply | |
| 389 | 0 | this._callBackStore._notReplied[db_command.getRequestId().toString()] = {start: new Date().getTime(), 'raw': raw, connection:connection, exhaust:exhaust}; |
| 390 | } | |
| 391 | ||
| 392 | /** | |
| 393 | * Re-Register a handler, on the cursor id f.ex | |
| 394 | * @ignore | |
| 395 | * @api private | |
| 396 | */ | |
| 397 | 1 | Base.prototype._reRegisterHandler = function(newId, object, callback) { |
| 398 | // Add the callback to the list of handlers | |
| 399 | 0 | this._callBackStore.once(newId, object.callback.listener); |
| 400 | // Add the information about the reply | |
| 401 | 0 | this._callBackStore._notReplied[newId] = object.info; |
| 402 | } | |
| 403 | ||
| 404 | /** | |
| 405 | * | |
| 406 | * @ignore | |
| 407 | * @api private | |
| 408 | */ | |
| 409 | 1 | Base.prototype._flushAllCallHandlers = function(err) { |
| 410 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 411 | ||
| 412 | 0 | for(var i = 0; i < keys.length; i++) { |
| 413 | 0 | this._callHandler(keys[i], null, err); |
| 414 | } | |
| 415 | } | |
| 416 | ||
| 417 | /** | |
| 418 | * | |
| 419 | * @ignore | |
| 420 | * @api private | |
| 421 | */ | |
| 422 | 1 | Base.prototype._callHandler = function(id, document, err) { |
| 423 | 0 | var self = this; |
| 424 | ||
| 425 | // If there is a callback peform it | |
| 426 | 0 | if(this._callBackStore.listeners(id).length >= 1) { |
| 427 | // Get info object | |
| 428 | 0 | var info = this._callBackStore._notReplied[id]; |
| 429 | // Delete the current object | |
| 430 | 0 | delete this._callBackStore._notReplied[id]; |
| 431 | // Call the handle directly don't emit | |
| 432 | 0 | var callback = this._callBackStore.listeners(id)[0].listener; |
| 433 | // Remove the listeners | |
| 434 | 0 | this._callBackStore.removeAllListeners(id); |
| 435 | // Force key deletion because it nulling it not deleting in 0.10.X | |
| 436 | 0 | if(this._callBackStore._events) { |
| 437 | 0 | delete this._callBackStore._events[id]; |
| 438 | } | |
| 439 | ||
| 440 | 0 | try { |
| 441 | // Execute the callback if one was provided | |
| 442 | 0 | if(typeof callback == 'function') callback(err, document, info.connection); |
| 443 | } catch(err) { | |
| 444 | 0 | self._emitAcrossAllDbInstances(self, null, "error", utils.toError(err), self, true, true); |
| 445 | } | |
| 446 | } | |
| 447 | } | |
| 448 | ||
| 449 | /** | |
| 450 | * | |
| 451 | * @ignore | |
| 452 | * @api private | |
| 453 | */ | |
| 454 | 1 | Base.prototype._hasHandler = function(id) { |
| 455 | 0 | return this._callBackStore.listeners(id).length >= 1; |
| 456 | } | |
| 457 | ||
| 458 | /** | |
| 459 | * | |
| 460 | * @ignore | |
| 461 | * @api private | |
| 462 | */ | |
| 463 | 1 | Base.prototype._removeHandler = function(id) { |
| 464 | // Remove the information | |
| 465 | 0 | if(this._callBackStore._notReplied[id] != null) delete this._callBackStore._notReplied[id]; |
| 466 | // Remove the callback if it's registered | |
| 467 | 0 | this._callBackStore.removeAllListeners(id); |
| 468 | // Force cleanup _events, node.js seems to set it as a null value | |
| 469 | 0 | if(this._callBackStore._events) { |
| 470 | 0 | delete this._callBackStore._events[id]; |
| 471 | } | |
| 472 | } | |
| 473 | ||
| 474 | /** | |
| 475 | * | |
| 476 | * @ignore | |
| 477 | * @api private | |
| 478 | */ | |
| 479 | 1 | Base.prototype._findHandler = function(id) { |
| 480 | 0 | var info = this._callBackStore._notReplied[id]; |
| 481 | // Return the callback | |
| 482 | 0 | return {info:info, callback:(this._callBackStore.listeners(id).length >= 1) ? this._callBackStore.listeners(id)[0] : null} |
| 483 | } | |
| 484 | ||
| 485 | /** | |
| 486 | * | |
| 487 | * @ignore | |
| 488 | * @api private | |
| 489 | */ | |
| 490 | 1 | Base.prototype._emitAcrossAllDbInstances = function(server, filterDb, event, message, object, resetConnection, rethrow_if_no_listeners) { |
| 491 | 0 | if(resetConnection) { |
| 492 | 0 | var dbs = this._dbStore.db(); |
| 493 | ||
| 494 | 0 | for(var i = 0; i < dbs.length; i++) { |
| 495 | 0 | if(typeof dbs[i].openCalled != 'undefined') |
| 496 | 0 | dbs[i].openCalled = false; |
| 497 | } | |
| 498 | } | |
| 499 | ||
| 500 | // Fire event | |
| 501 | 0 | this._dbStore.emit(event, message, object, resetConnection, filterDb, rethrow_if_no_listeners); |
| 502 | } | |
| 503 | ||
| 504 | 1 | exports.Base = Base; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var utils = require('./connection_utils'), |
| 2 | inherits = require('util').inherits, | |
| 3 | net = require('net'), | |
| 4 | EventEmitter = require('events').EventEmitter, | |
| 5 | inherits = require('util').inherits, | |
| 6 | binaryutils = require('../utils'), | |
| 7 | tls = require('tls'); | |
| 8 | ||
| 9 | 1 | var Connection = exports.Connection = function(id, socketOptions) { |
| 10 | 0 | var self = this; |
| 11 | // Set up event emitter | |
| 12 | 0 | EventEmitter.call(this); |
| 13 | // Store all socket options | |
| 14 | 0 | this.socketOptions = socketOptions ? socketOptions : {host:'localhost', port:27017, domainSocket:false}; |
| 15 | // Set keep alive default if not overriden | |
| 16 | 0 | if(this.socketOptions.keepAlive == null && (process.platform !== "sunos" || process.platform !== "win32")) this.socketOptions.keepAlive = 100; |
| 17 | // Id for the connection | |
| 18 | 0 | this.id = id; |
| 19 | // State of the connection | |
| 20 | 0 | this.connected = false; |
| 21 | // Set if this is a domain socket | |
| 22 | 0 | this.domainSocket = this.socketOptions.domainSocket; |
| 23 | ||
| 24 | // Supported min and max wire protocol | |
| 25 | 0 | this.minWireVersion = 0; |
| 26 | 0 | this.maxWireVersion = 2; |
| 27 | ||
| 28 | // | |
| 29 | // Connection parsing state | |
| 30 | // | |
| 31 | 0 | this.maxBsonSize = socketOptions.maxBsonSize ? socketOptions.maxBsonSize : Connection.DEFAULT_MAX_BSON_SIZE; |
| 32 | 0 | this.maxMessageSizeBytes = socketOptions.maxMessageSizeBytes ? socketOptions.maxMessageSizeBytes : Connection.DEFAULT_MAX_MESSAGE_SIZE; |
| 33 | // Contains the current message bytes | |
| 34 | 0 | this.buffer = null; |
| 35 | // Contains the current message size | |
| 36 | 0 | this.sizeOfMessage = 0; |
| 37 | // Contains the readIndex for the messaage | |
| 38 | 0 | this.bytesRead = 0; |
| 39 | // Contains spill over bytes from additional messages | |
| 40 | 0 | this.stubBuffer = 0; |
| 41 | ||
| 42 | // Just keeps list of events we allow | |
| 43 | 0 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[], end:[]}; |
| 44 | ||
| 45 | // Just keeps list of events we allow | |
| 46 | 0 | resetHandlers(this, false); |
| 47 | // Bson object | |
| 48 | 0 | this.maxBsonSettings = { |
| 49 | disableDriverBSONSizeCheck: this.socketOptions['disableDriverBSONSizeCheck'] || false | |
| 50 | , maxBsonSize: this.maxBsonSize | |
| 51 | , maxMessageSizeBytes: this.maxMessageSizeBytes | |
| 52 | } | |
| 53 | ||
| 54 | // Allow setting the socketTimeoutMS on all connections | |
| 55 | // to work around issues such as secondaries blocking due to compaction | |
| 56 | 0 | Object.defineProperty(this, "socketTimeoutMS", { |
| 57 | enumerable: true | |
| 58 | 0 | , get: function () { return self.socketOptions.socketTimeoutMS; } |
| 59 | , set: function (value) { | |
| 60 | // Set the socket timeoutMS value | |
| 61 | 0 | self.socketOptions.socketTimeoutMS = value; |
| 62 | // Set the physical connection timeout | |
| 63 | 0 | self.connection.setTimeout(self.socketOptions.socketTimeoutMS); |
| 64 | } | |
| 65 | }); | |
| 66 | } | |
| 67 | ||
| 68 | // Set max bson size | |
| 69 | 1 | Connection.DEFAULT_MAX_BSON_SIZE = 1024 * 1024 * 4; |
| 70 | // Set default to max bson to avoid overflow or bad guesses | |
| 71 | 1 | Connection.DEFAULT_MAX_MESSAGE_SIZE = Connection.DEFAULT_MAX_BSON_SIZE; |
| 72 | ||
| 73 | // Inherit event emitter so we can emit stuff wohoo | |
| 74 | 1 | inherits(Connection, EventEmitter); |
| 75 | ||
| 76 | 1 | Connection.prototype.start = function() { |
| 77 | 0 | var self = this; |
| 78 | ||
| 79 | // If we have a normal connection | |
| 80 | 0 | if(this.socketOptions.ssl) { |
| 81 | // Create new connection instance | |
| 82 | 0 | if(this.domainSocket) { |
| 83 | 0 | this.connection = net.createConnection(this.socketOptions.host); |
| 84 | } else { | |
| 85 | 0 | this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); |
| 86 | } | |
| 87 | 0 | if(this.logger != null && this.logger.doDebug){ |
| 88 | 0 | this.logger.debug("opened connection", this.socketOptions); |
| 89 | } | |
| 90 | ||
| 91 | // Set options on the socket | |
| 92 | 0 | this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout); |
| 93 | // Work around for 0.4.X | |
| 94 | 0 | if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); |
| 95 | // Set keep alive if defined | |
| 96 | 0 | if(process.version.indexOf("v0.4") == -1) { |
| 97 | 0 | if(this.socketOptions.keepAlive > 0) { |
| 98 | 0 | this.connection.setKeepAlive(true, this.socketOptions.keepAlive); |
| 99 | } else { | |
| 100 | 0 | this.connection.setKeepAlive(false); |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | // Check if the driver should validate the certificate | |
| 105 | 0 | var validate_certificates = this.socketOptions.sslValidate == true ? true : false; |
| 106 | ||
| 107 | // Create options for the tls connection | |
| 108 | 0 | var tls_options = { |
| 109 | socket: this.connection | |
| 110 | , rejectUnauthorized: false | |
| 111 | } | |
| 112 | ||
| 113 | // If we wish to validate the certificate we have provided a ca store | |
| 114 | 0 | if(validate_certificates) { |
| 115 | 0 | tls_options.ca = this.socketOptions.sslCA; |
| 116 | } | |
| 117 | ||
| 118 | // If we have a certificate to present | |
| 119 | 0 | if(this.socketOptions.sslCert) { |
| 120 | 0 | tls_options.cert = this.socketOptions.sslCert; |
| 121 | 0 | tls_options.key = this.socketOptions.sslKey; |
| 122 | } | |
| 123 | ||
| 124 | // If the driver has been provided a private key password | |
| 125 | 0 | if(this.socketOptions.sslPass) { |
| 126 | 0 | tls_options.passphrase = this.socketOptions.sslPass; |
| 127 | } | |
| 128 | ||
| 129 | // Contains the cleartext stream | |
| 130 | 0 | var cleartext = null; |
| 131 | // Attempt to establish a TLS connection to the server | |
| 132 | 0 | try { |
| 133 | 0 | cleartext = tls.connect(this.socketOptions.port, this.socketOptions.host, tls_options, function() { |
| 134 | // If we have a ssl certificate validation error return an error | |
| 135 | 0 | if(cleartext.authorizationError && validate_certificates) { |
| 136 | // Emit an error | |
| 137 | 0 | return self.emit("error", cleartext.authorizationError, self, {ssl:true}); |
| 138 | } | |
| 139 | ||
| 140 | // Connect to the server | |
| 141 | 0 | connectHandler(self)(); |
| 142 | }) | |
| 143 | } catch(err) { | |
| 144 | 0 | return self.emit("error", "SSL connection failed", self, {ssl:true}); |
| 145 | } | |
| 146 | ||
| 147 | // Save the output stream | |
| 148 | 0 | this.writeSteam = cleartext; |
| 149 | ||
| 150 | // Set up data handler for the clear stream | |
| 151 | 0 | cleartext.on("data", createDataHandler(this)); |
| 152 | // Do any handling of end event of the stream | |
| 153 | 0 | cleartext.on("end", endHandler(this)); |
| 154 | 0 | cleartext.on("error", errorHandler(this)); |
| 155 | ||
| 156 | // Handle any errors | |
| 157 | 0 | this.connection.on("error", errorHandler(this)); |
| 158 | // Handle timeout | |
| 159 | 0 | this.connection.on("timeout", timeoutHandler(this)); |
| 160 | // Handle drain event | |
| 161 | 0 | this.connection.on("drain", drainHandler(this)); |
| 162 | // Handle the close event | |
| 163 | 0 | this.connection.on("close", closeHandler(this)); |
| 164 | } else { | |
| 165 | // Create new connection instance | |
| 166 | 0 | if(this.domainSocket) { |
| 167 | 0 | this.connection = net.createConnection(this.socketOptions.host); |
| 168 | } else { | |
| 169 | 0 | this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); |
| 170 | } | |
| 171 | 0 | if(this.logger != null && this.logger.doDebug){ |
| 172 | 0 | this.logger.debug("opened connection", this.socketOptions); |
| 173 | } | |
| 174 | ||
| 175 | // Set options on the socket | |
| 176 | 0 | this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout); |
| 177 | // Work around for 0.4.X | |
| 178 | 0 | if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); |
| 179 | // Set keep alive if defined | |
| 180 | 0 | if(process.version.indexOf("v0.4") == -1) { |
| 181 | 0 | if(this.socketOptions.keepAlive > 0) { |
| 182 | 0 | this.connection.setKeepAlive(true, this.socketOptions.keepAlive); |
| 183 | } else { | |
| 184 | 0 | this.connection.setKeepAlive(false); |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | // Set up write stream | |
| 189 | 0 | this.writeSteam = this.connection; |
| 190 | // Add handlers | |
| 191 | 0 | this.connection.on("error", errorHandler(this)); |
| 192 | // Add all handlers to the socket to manage it | |
| 193 | 0 | this.connection.on("connect", connectHandler(this)); |
| 194 | // this.connection.on("end", endHandler(this)); | |
| 195 | 0 | this.connection.on("data", createDataHandler(this)); |
| 196 | 0 | this.connection.on("timeout", timeoutHandler(this)); |
| 197 | 0 | this.connection.on("drain", drainHandler(this)); |
| 198 | 0 | this.connection.on("close", closeHandler(this)); |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | // Check if the sockets are live | |
| 203 | 1 | Connection.prototype.isConnected = function() { |
| 204 | 0 | return this.connected && !this.connection.destroyed && this.connection.writable && this.connection.readable; |
| 205 | } | |
| 206 | ||
| 207 | // Validate if the driver supports this server | |
| 208 | 1 | Connection.prototype.isCompatible = function() { |
| 209 | 0 | if(this.serverCapabilities == null) return true; |
| 210 | // Is compatible with backward server | |
| 211 | 0 | if(this.serverCapabilities.minWireVersion == 0 |
| 212 | 0 | && this.serverCapabilities.maxWireVersion ==0) return true; |
| 213 | ||
| 214 | // Check if we overlap | |
| 215 | 0 | if(this.serverCapabilities.minWireVersion >= this.minWireVersion |
| 216 | 0 | && this.serverCapabilities.maxWireVersion <= this.maxWireVersion) return true; |
| 217 | ||
| 218 | // Not compatible | |
| 219 | 0 | return false; |
| 220 | } | |
| 221 | ||
| 222 | // Write the data out to the socket | |
| 223 | 1 | Connection.prototype.write = function(command, callback) { |
| 224 | 0 | try { |
| 225 | // If we have a list off commands to be executed on the same socket | |
| 226 | 0 | if(Array.isArray(command)) { |
| 227 | 0 | for(var i = 0; i < command.length; i++) { |
| 228 | 0 | try { |
| 229 | // Pass in the bson validation settings (validate early) | |
| 230 | 0 | var binaryCommand = command[i].toBinary(this.maxBsonSettings) |
| 231 | ||
| 232 | 0 | if(this.logger != null && this.logger.doDebug) |
| 233 | 0 | this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]}); |
| 234 | ||
| 235 | 0 | this.writeSteam.write(binaryCommand); |
| 236 | } catch(err) { | |
| 237 | 0 | return callback(err, null); |
| 238 | } | |
| 239 | } | |
| 240 | } else { | |
| 241 | 0 | try { |
| 242 | // Pass in the bson validation settings (validate early) | |
| 243 | 0 | var binaryCommand = command.toBinary(this.maxBsonSettings) |
| 244 | // Do we have a logger active log the event | |
| 245 | 0 | if(this.logger != null && this.logger.doDebug) |
| 246 | 0 | this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]}); |
| 247 | // Write the binary command out to socket | |
| 248 | 0 | this.writeSteam.write(binaryCommand); |
| 249 | } catch(err) { | |
| 250 | 0 | return callback(err, null) |
| 251 | } | |
| 252 | } | |
| 253 | } catch (err) { | |
| 254 | 0 | if(typeof callback === 'function') callback(err); |
| 255 | } | |
| 256 | } | |
| 257 | ||
| 258 | // Force the closure of the connection | |
| 259 | 1 | Connection.prototype.close = function() { |
| 260 | // clear out all the listeners | |
| 261 | 0 | resetHandlers(this, true); |
| 262 | // Add a dummy error listener to catch any weird last moment errors (and ignore them) | |
| 263 | 0 | this.connection.on("error", function() {}) |
| 264 | // destroy connection | |
| 265 | 0 | this.connection.destroy(); |
| 266 | 0 | if(this.logger != null && this.logger.doDebug){ |
| 267 | 0 | this.logger.debug("closed connection", this.connection); |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | // Reset all handlers | |
| 272 | 1 | var resetHandlers = function(self, clearListeners) { |
| 273 | 0 | self.eventHandlers = {error:[], connect:[], close:[], end:[], timeout:[], parseError:[], message:[]}; |
| 274 | ||
| 275 | // If we want to clear all the listeners | |
| 276 | 0 | if(clearListeners && self.connection != null) { |
| 277 | 0 | var keys = Object.keys(self.eventHandlers); |
| 278 | // Remove all listeners | |
| 279 | 0 | for(var i = 0; i < keys.length; i++) { |
| 280 | 0 | self.connection.removeAllListeners(keys[i]); |
| 281 | } | |
| 282 | } | |
| 283 | } | |
| 284 | ||
| 285 | // | |
| 286 | // Handlers | |
| 287 | // | |
| 288 | ||
| 289 | // Connect handler | |
| 290 | 1 | var connectHandler = function(self) { |
| 291 | 0 | return function(data) { |
| 292 | // Set connected | |
| 293 | 0 | self.connected = true; |
| 294 | // Now that we are connected set the socket timeout | |
| 295 | 0 | self.connection.setTimeout(self.socketOptions.socketTimeoutMS != null ? self.socketOptions.socketTimeoutMS : self.socketOptions.timeout); |
| 296 | // Emit the connect event with no error | |
| 297 | 0 | self.emit("connect", null, self); |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | 1 | var createDataHandler = exports.Connection.createDataHandler = function(self) { |
| 302 | // We need to handle the parsing of the data | |
| 303 | // and emit the messages when there is a complete one | |
| 304 | 0 | return function(data) { |
| 305 | // Parse until we are done with the data | |
| 306 | 0 | while(data.length > 0) { |
| 307 | // If we still have bytes to read on the current message | |
| 308 | 0 | if(self.bytesRead > 0 && self.sizeOfMessage > 0) { |
| 309 | // Calculate the amount of remaining bytes | |
| 310 | 0 | var remainingBytesToRead = self.sizeOfMessage - self.bytesRead; |
| 311 | // Check if the current chunk contains the rest of the message | |
| 312 | 0 | if(remainingBytesToRead > data.length) { |
| 313 | // Copy the new data into the exiting buffer (should have been allocated when we know the message size) | |
| 314 | 0 | data.copy(self.buffer, self.bytesRead); |
| 315 | // Adjust the number of bytes read so it point to the correct index in the buffer | |
| 316 | 0 | self.bytesRead = self.bytesRead + data.length; |
| 317 | ||
| 318 | // Reset state of buffer | |
| 319 | 0 | data = new Buffer(0); |
| 320 | } else { | |
| 321 | // Copy the missing part of the data into our current buffer | |
| 322 | 0 | data.copy(self.buffer, self.bytesRead, 0, remainingBytesToRead); |
| 323 | // Slice the overflow into a new buffer that we will then re-parse | |
| 324 | 0 | data = data.slice(remainingBytesToRead); |
| 325 | ||
| 326 | // Emit current complete message | |
| 327 | 0 | try { |
| 328 | 0 | var emitBuffer = self.buffer; |
| 329 | // Reset state of buffer | |
| 330 | 0 | self.buffer = null; |
| 331 | 0 | self.sizeOfMessage = 0; |
| 332 | 0 | self.bytesRead = 0; |
| 333 | 0 | self.stubBuffer = null; |
| 334 | // Emit the buffer | |
| 335 | 0 | self.emit("message", emitBuffer, self); |
| 336 | } catch(err) { | |
| 337 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 338 | sizeOfMessage:self.sizeOfMessage, | |
| 339 | bytesRead:self.bytesRead, | |
| 340 | stubBuffer:self.stubBuffer}}; | |
| 341 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 342 | // We got a parse Error fire it off then keep going | |
| 343 | 0 | self.emit("parseError", errorObject, self); |
| 344 | } | |
| 345 | } | |
| 346 | } else { | |
| 347 | // Stub buffer is kept in case we don't get enough bytes to determine the | |
| 348 | // size of the message (< 4 bytes) | |
| 349 | 0 | if(self.stubBuffer != null && self.stubBuffer.length > 0) { |
| 350 | ||
| 351 | // If we have enough bytes to determine the message size let's do it | |
| 352 | 0 | if(self.stubBuffer.length + data.length > 4) { |
| 353 | // Prepad the data | |
| 354 | 0 | var newData = new Buffer(self.stubBuffer.length + data.length); |
| 355 | 0 | self.stubBuffer.copy(newData, 0); |
| 356 | 0 | data.copy(newData, self.stubBuffer.length); |
| 357 | // Reassign for parsing | |
| 358 | 0 | data = newData; |
| 359 | ||
| 360 | // Reset state of buffer | |
| 361 | 0 | self.buffer = null; |
| 362 | 0 | self.sizeOfMessage = 0; |
| 363 | 0 | self.bytesRead = 0; |
| 364 | 0 | self.stubBuffer = null; |
| 365 | ||
| 366 | } else { | |
| 367 | ||
| 368 | // Add the the bytes to the stub buffer | |
| 369 | 0 | var newStubBuffer = new Buffer(self.stubBuffer.length + data.length); |
| 370 | // Copy existing stub buffer | |
| 371 | 0 | self.stubBuffer.copy(newStubBuffer, 0); |
| 372 | // Copy missing part of the data | |
| 373 | 0 | data.copy(newStubBuffer, self.stubBuffer.length); |
| 374 | // Exit parsing loop | |
| 375 | 0 | data = new Buffer(0); |
| 376 | } | |
| 377 | } else { | |
| 378 | 0 | if(data.length > 4) { |
| 379 | // Retrieve the message size | |
| 380 | 0 | var sizeOfMessage = binaryutils.decodeUInt32(data, 0); |
| 381 | // If we have a negative sizeOfMessage emit error and return | |
| 382 | 0 | if(sizeOfMessage < 0 || sizeOfMessage > self.maxBsonSize) { |
| 383 | 0 | var errorObject = {err:"socketHandler", trace:'', bin:self.buffer, parseState:{ |
| 384 | sizeOfMessage: sizeOfMessage, | |
| 385 | bytesRead: self.bytesRead, | |
| 386 | stubBuffer: self.stubBuffer}}; | |
| 387 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 388 | // We got a parse Error fire it off then keep going | |
| 389 | 0 | self.emit("parseError", errorObject, self); |
| 390 | 0 | return; |
| 391 | } | |
| 392 | ||
| 393 | // Ensure that the size of message is larger than 0 and less than the max allowed | |
| 394 | 0 | if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage > data.length) { |
| 395 | 0 | self.buffer = new Buffer(sizeOfMessage); |
| 396 | // Copy all the data into the buffer | |
| 397 | 0 | data.copy(self.buffer, 0); |
| 398 | // Update bytes read | |
| 399 | 0 | self.bytesRead = data.length; |
| 400 | // Update sizeOfMessage | |
| 401 | 0 | self.sizeOfMessage = sizeOfMessage; |
| 402 | // Ensure stub buffer is null | |
| 403 | 0 | self.stubBuffer = null; |
| 404 | // Exit parsing loop | |
| 405 | 0 | data = new Buffer(0); |
| 406 | ||
| 407 | 0 | } else if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage == data.length) { |
| 408 | 0 | try { |
| 409 | 0 | var emitBuffer = data; |
| 410 | // Reset state of buffer | |
| 411 | 0 | self.buffer = null; |
| 412 | 0 | self.sizeOfMessage = 0; |
| 413 | 0 | self.bytesRead = 0; |
| 414 | 0 | self.stubBuffer = null; |
| 415 | // Exit parsing loop | |
| 416 | 0 | data = new Buffer(0); |
| 417 | // Emit the message | |
| 418 | 0 | self.emit("message", emitBuffer, self); |
| 419 | } catch (err) { | |
| 420 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 421 | sizeOfMessage:self.sizeOfMessage, | |
| 422 | bytesRead:self.bytesRead, | |
| 423 | stubBuffer:self.stubBuffer}}; | |
| 424 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 425 | // We got a parse Error fire it off then keep going | |
| 426 | 0 | self.emit("parseError", errorObject, self); |
| 427 | } | |
| 428 | 0 | } else if(sizeOfMessage <= 4 || sizeOfMessage > self.maxBsonSize) { |
| 429 | 0 | var errorObject = {err:"socketHandler", trace:null, bin:data, parseState:{ |
| 430 | sizeOfMessage:sizeOfMessage, | |
| 431 | bytesRead:0, | |
| 432 | buffer:null, | |
| 433 | stubBuffer:null}}; | |
| 434 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 435 | // We got a parse Error fire it off then keep going | |
| 436 | 0 | self.emit("parseError", errorObject, self); |
| 437 | ||
| 438 | // Clear out the state of the parser | |
| 439 | 0 | self.buffer = null; |
| 440 | 0 | self.sizeOfMessage = 0; |
| 441 | 0 | self.bytesRead = 0; |
| 442 | 0 | self.stubBuffer = null; |
| 443 | // Exit parsing loop | |
| 444 | 0 | data = new Buffer(0); |
| 445 | ||
| 446 | } else { | |
| 447 | 0 | try { |
| 448 | 0 | var emitBuffer = data.slice(0, sizeOfMessage); |
| 449 | // Reset state of buffer | |
| 450 | 0 | self.buffer = null; |
| 451 | 0 | self.sizeOfMessage = 0; |
| 452 | 0 | self.bytesRead = 0; |
| 453 | 0 | self.stubBuffer = null; |
| 454 | // Copy rest of message | |
| 455 | 0 | data = data.slice(sizeOfMessage); |
| 456 | // Emit the message | |
| 457 | 0 | self.emit("message", emitBuffer, self); |
| 458 | } catch (err) { | |
| 459 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 460 | sizeOfMessage:sizeOfMessage, | |
| 461 | bytesRead:self.bytesRead, | |
| 462 | stubBuffer:self.stubBuffer}}; | |
| 463 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 464 | // We got a parse Error fire it off then keep going | |
| 465 | 0 | self.emit("parseError", errorObject, self); |
| 466 | } | |
| 467 | ||
| 468 | } | |
| 469 | } else { | |
| 470 | // Create a buffer that contains the space for the non-complete message | |
| 471 | 0 | self.stubBuffer = new Buffer(data.length) |
| 472 | // Copy the data to the stub buffer | |
| 473 | 0 | data.copy(self.stubBuffer, 0); |
| 474 | // Exit parsing loop | |
| 475 | 0 | data = new Buffer(0); |
| 476 | } | |
| 477 | } | |
| 478 | } | |
| 479 | } | |
| 480 | } | |
| 481 | } | |
| 482 | ||
| 483 | 1 | var endHandler = function(self) { |
| 484 | 0 | return function() { |
| 485 | // Set connected to false | |
| 486 | 0 | self.connected = false; |
| 487 | // Emit end event | |
| 488 | 0 | self.emit("end", {err: 'connection received Fin packet from [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 489 | } | |
| 490 | } | |
| 491 | ||
| 492 | 1 | var timeoutHandler = function(self) { |
| 493 | 0 | return function() { |
| 494 | // Set connected to false | |
| 495 | 0 | self.connected = false; |
| 496 | // Emit timeout event | |
| 497 | 0 | self.emit("timeout", {err: 'connection to [' + self.socketOptions.host + ':' + self.socketOptions.port + '] timed out'}, self); |
| 498 | } | |
| 499 | } | |
| 500 | ||
| 501 | 1 | var drainHandler = function(self) { |
| 502 | 0 | return function() { |
| 503 | } | |
| 504 | } | |
| 505 | ||
| 506 | 1 | var errorHandler = function(self) { |
| 507 | 0 | return function(err) { |
| 508 | 0 | self.connection.destroy(); |
| 509 | // Set connected to false | |
| 510 | 0 | self.connected = false; |
| 511 | // Emit error | |
| 512 | 0 | self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 513 | } | |
| 514 | } | |
| 515 | ||
| 516 | 1 | var closeHandler = function(self) { |
| 517 | 0 | return function(hadError) { |
| 518 | // If we have an error during the connection phase | |
| 519 | 0 | if(hadError && !self.connected) { |
| 520 | // Set disconnected | |
| 521 | 0 | self.connected = false; |
| 522 | // Emit error | |
| 523 | 0 | self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 524 | } else { | |
| 525 | // Set disconnected | |
| 526 | 0 | self.connected = false; |
| 527 | // Emit close | |
| 528 | 0 | self.emit("close", {err: 'connection closed to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 529 | } | |
| 530 | } | |
| 531 | } | |
| 532 | ||
| 533 | // Some basic defaults | |
| 534 | 1 | Connection.DEFAULT_PORT = 27017; |
| 535 | ||
| 536 | ||
| 537 | ||
| 538 | ||
| 539 | ||
| 540 | ||
| 541 | ||
| 542 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var utils = require('./connection_utils'), |
| 2 | inherits = require('util').inherits, | |
| 3 | net = require('net'), | |
| 4 | timers = require('timers'), | |
| 5 | EventEmitter = require('events').EventEmitter, | |
| 6 | inherits = require('util').inherits, | |
| 7 | MongoReply = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/lib/mongodb/connection/../responses/mongo_reply").MongoReply, | |
| 8 | Connection = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/lib/mongodb/connection/./connection").Connection; | |
| 9 | ||
| 10 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 11 | 1 | var processor = require('../utils').processor(); |
| 12 | ||
| 13 | 1 | var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bson, socketOptions) { |
| 14 | 0 | if(typeof host !== 'string') { |
| 15 | 0 | throw new Error("host must be specified [" + host + "]"); |
| 16 | } | |
| 17 | ||
| 18 | // Set up event emitter | |
| 19 | 0 | EventEmitter.call(this); |
| 20 | ||
| 21 | // Keep all options for the socket in a specific collection allowing the user to specify the | |
| 22 | // Wished upon socket connection parameters | |
| 23 | 0 | this.socketOptions = typeof socketOptions === 'object' ? socketOptions : {}; |
| 24 | 0 | this.socketOptions.host = host; |
| 25 | 0 | this.socketOptions.port = port; |
| 26 | 0 | this.socketOptions.domainSocket = false; |
| 27 | 0 | this.bson = bson; |
| 28 | // PoolSize is always + 1 for special reserved "measurment" socket (like ping, stats etc) | |
| 29 | 0 | this.poolSize = poolSize; |
| 30 | 0 | this.minPoolSize = Math.floor(this.poolSize / 2) + 1; |
| 31 | ||
| 32 | // Check if the host is a socket | |
| 33 | 0 | if(host.match(/^\//)) { |
| 34 | 0 | this.socketOptions.domainSocket = true; |
| 35 | 0 | } else if(typeof port === 'string') { |
| 36 | 0 | try { |
| 37 | 0 | port = parseInt(port, 10); |
| 38 | } catch(err) { | |
| 39 | 0 | new Error("port must be specified or valid integer[" + port + "]"); |
| 40 | } | |
| 41 | 0 | } else if(typeof port !== 'number') { |
| 42 | 0 | throw new Error("port must be specified [" + port + "]"); |
| 43 | } | |
| 44 | ||
| 45 | // Set default settings for the socket options | |
| 46 | 0 | utils.setIntegerParameter(this.socketOptions, 'timeout', 0); |
| 47 | // Delay before writing out the data to the server | |
| 48 | 0 | utils.setBooleanParameter(this.socketOptions, 'noDelay', true); |
| 49 | // Delay before writing out the data to the server | |
| 50 | 0 | utils.setIntegerParameter(this.socketOptions, 'keepAlive', 0); |
| 51 | // Set the encoding of the data read, default is binary == null | |
| 52 | 0 | utils.setStringParameter(this.socketOptions, 'encoding', null); |
| 53 | // Allows you to set a throttling bufferSize if you need to stop overflows | |
| 54 | 0 | utils.setIntegerParameter(this.socketOptions, 'bufferSize', 0); |
| 55 | ||
| 56 | // Internal structures | |
| 57 | 0 | this.openConnections = []; |
| 58 | // Assign connection id's | |
| 59 | 0 | this.connectionId = 0; |
| 60 | ||
| 61 | // Current index for selection of pool connection | |
| 62 | 0 | this.currentConnectionIndex = 0; |
| 63 | // The pool state | |
| 64 | 0 | this._poolState = 'disconnected'; |
| 65 | // timeout control | |
| 66 | 0 | this._timeout = false; |
| 67 | // Time to wait between connections for the pool | |
| 68 | 0 | this._timeToWait = 10; |
| 69 | } | |
| 70 | ||
| 71 | 1 | inherits(ConnectionPool, EventEmitter); |
| 72 | ||
| 73 | 1 | ConnectionPool.prototype.setMaxBsonSize = function(maxBsonSize) { |
| 74 | 0 | if(maxBsonSize == null){ |
| 75 | 0 | maxBsonSize = Connection.DEFAULT_MAX_BSON_SIZE; |
| 76 | } | |
| 77 | ||
| 78 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 79 | 0 | this.openConnections[i].maxBsonSize = maxBsonSize; |
| 80 | 0 | this.openConnections[i].maxBsonSettings.maxBsonSize = maxBsonSize; |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | 1 | ConnectionPool.prototype.setMaxMessageSizeBytes = function(maxMessageSizeBytes) { |
| 85 | 0 | if(maxMessageSizeBytes == null){ |
| 86 | 0 | maxMessageSizeBytes = Connection.DEFAULT_MAX_MESSAGE_SIZE; |
| 87 | } | |
| 88 | ||
| 89 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 90 | 0 | this.openConnections[i].maxMessageSizeBytes = maxMessageSizeBytes; |
| 91 | 0 | this.openConnections[i].maxBsonSettings.maxMessageSizeBytes = maxMessageSizeBytes; |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | // Start a function | |
| 96 | 1 | var _connect = function(_self) { |
| 97 | // return new function() { | |
| 98 | // Create a new connection instance | |
| 99 | 0 | var connection = new Connection(_self.connectionId++, _self.socketOptions); |
| 100 | // Set logger on pool | |
| 101 | 0 | connection.logger = _self.logger; |
| 102 | // Connect handler | |
| 103 | 0 | connection.on("connect", function(err, connection) { |
| 104 | // Add connection to list of open connections | |
| 105 | 0 | _self.openConnections.push(connection); |
| 106 | // If the number of open connections is equal to the poolSize signal ready pool | |
| 107 | 0 | if(_self.openConnections.length === _self.poolSize && _self._poolState !== 'disconnected') { |
| 108 | // Set connected | |
| 109 | 0 | _self._poolState = 'connected'; |
| 110 | // Emit pool ready | |
| 111 | 0 | _self.emit("poolReady"); |
| 112 | 0 | } else if(_self.openConnections.length < _self.poolSize) { |
| 113 | // Wait a little bit of time to let the close event happen if the server closes the connection | |
| 114 | // so we don't leave hanging connections around | |
| 115 | 0 | if(typeof _self._timeToWait == 'number') { |
| 116 | 0 | setTimeout(function() { |
| 117 | // If we are still connecting (no close events fired in between start another connection) | |
| 118 | 0 | if(_self._poolState == 'connecting') { |
| 119 | 0 | _connect(_self); |
| 120 | } | |
| 121 | }, _self._timeToWait); | |
| 122 | } else { | |
| 123 | 0 | processor(function() { |
| 124 | // If we are still connecting (no close events fired in between start another connection) | |
| 125 | 0 | if(_self._poolState == 'connecting') { |
| 126 | 0 | _connect(_self); |
| 127 | } | |
| 128 | }); | |
| 129 | } | |
| 130 | } | |
| 131 | }); | |
| 132 | ||
| 133 | 0 | var numberOfErrors = 0 |
| 134 | ||
| 135 | // Error handler | |
| 136 | 0 | connection.on("error", function(err, connection, error_options) { |
| 137 | 0 | numberOfErrors++; |
| 138 | // If we are already disconnected ignore the event | |
| 139 | 0 | if(_self._poolState != 'disconnected' && _self.listeners("error").length > 0) { |
| 140 | 0 | _self.emit("error", err, connection, error_options); |
| 141 | } | |
| 142 | ||
| 143 | // Close the connection | |
| 144 | 0 | connection.close(); |
| 145 | // Set pool as disconnected | |
| 146 | 0 | _self._poolState = 'disconnected'; |
| 147 | // Stop the pool | |
| 148 | 0 | _self.stop(); |
| 149 | }); | |
| 150 | ||
| 151 | // Close handler | |
| 152 | 0 | connection.on("close", function() { |
| 153 | // If we are already disconnected ignore the event | |
| 154 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("close").length > 0) { |
| 155 | 0 | _self.emit("close"); |
| 156 | } | |
| 157 | ||
| 158 | // Set disconnected | |
| 159 | 0 | _self._poolState = 'disconnected'; |
| 160 | // Stop | |
| 161 | 0 | _self.stop(); |
| 162 | }); | |
| 163 | ||
| 164 | // Timeout handler | |
| 165 | 0 | connection.on("timeout", function(err, connection) { |
| 166 | // If we are already disconnected ignore the event | |
| 167 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("timeout").length > 0) { |
| 168 | 0 | _self.emit("timeout", err); |
| 169 | } | |
| 170 | ||
| 171 | // Close the connection | |
| 172 | 0 | connection.close(); |
| 173 | // Set disconnected | |
| 174 | 0 | _self._poolState = 'disconnected'; |
| 175 | 0 | _self.stop(); |
| 176 | }); | |
| 177 | ||
| 178 | // Parse error, needs a complete shutdown of the pool | |
| 179 | 0 | connection.on("parseError", function() { |
| 180 | // If we are already disconnected ignore the event | |
| 181 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("parseError").length > 0) { |
| 182 | 0 | _self.emit("parseError", new Error("parseError occured")); |
| 183 | } | |
| 184 | ||
| 185 | // Set disconnected | |
| 186 | 0 | _self._poolState = 'disconnected'; |
| 187 | 0 | _self.stop(); |
| 188 | }); | |
| 189 | ||
| 190 | 0 | connection.on("message", function(message) { |
| 191 | 0 | _self.emit("message", message); |
| 192 | }); | |
| 193 | ||
| 194 | // Start connection in the next tick | |
| 195 | 0 | connection.start(); |
| 196 | // }(); | |
| 197 | } | |
| 198 | ||
| 199 | ||
| 200 | // Start method, will throw error if no listeners are available | |
| 201 | // Pass in an instance of the listener that contains the api for | |
| 202 | // finding callbacks for a given message etc. | |
| 203 | 1 | ConnectionPool.prototype.start = function() { |
| 204 | 0 | var markerDate = new Date().getTime(); |
| 205 | 0 | var self = this; |
| 206 | ||
| 207 | 0 | if(this.listeners("poolReady").length == 0) { |
| 208 | 0 | throw "pool must have at least one listener ready that responds to the [poolReady] event"; |
| 209 | } | |
| 210 | ||
| 211 | // Set pool state to connecting | |
| 212 | 0 | this._poolState = 'connecting'; |
| 213 | 0 | this._timeout = false; |
| 214 | ||
| 215 | 0 | _connect(self); |
| 216 | } | |
| 217 | ||
| 218 | // Restart a connection pool (on a close the pool might be in a wrong state) | |
| 219 | 1 | ConnectionPool.prototype.restart = function() { |
| 220 | // Close all connections | |
| 221 | 0 | this.stop(false); |
| 222 | // Now restart the pool | |
| 223 | 0 | this.start(); |
| 224 | } | |
| 225 | ||
| 226 | // Stop the connections in the pool | |
| 227 | 1 | ConnectionPool.prototype.stop = function(removeListeners) { |
| 228 | 0 | removeListeners = removeListeners == null ? true : removeListeners; |
| 229 | // Set disconnected | |
| 230 | 0 | this._poolState = 'disconnected'; |
| 231 | ||
| 232 | // Clear all listeners if specified | |
| 233 | 0 | if(removeListeners) { |
| 234 | 0 | this.removeAllEventListeners(); |
| 235 | } | |
| 236 | ||
| 237 | // Close all connections | |
| 238 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 239 | 0 | this.openConnections[i].close(); |
| 240 | } | |
| 241 | ||
| 242 | // Clean up | |
| 243 | 0 | this.openConnections = []; |
| 244 | } | |
| 245 | ||
| 246 | // Check the status of the connection | |
| 247 | 1 | ConnectionPool.prototype.isConnected = function() { |
| 248 | // return this._poolState === 'connected'; | |
| 249 | 0 | return this.openConnections.length > 0 && this.openConnections[0].isConnected(); |
| 250 | } | |
| 251 | ||
| 252 | // Checkout a connection from the pool for usage, or grab a specific pool instance | |
| 253 | 1 | ConnectionPool.prototype.checkoutConnection = function(id) { |
| 254 | 0 | var index = (this.currentConnectionIndex++ % (this.openConnections.length)); |
| 255 | 0 | var connection = this.openConnections[index]; |
| 256 | 0 | return connection; |
| 257 | } | |
| 258 | ||
| 259 | 1 | ConnectionPool.prototype.getAllConnections = function() { |
| 260 | 0 | return this.openConnections; |
| 261 | } | |
| 262 | ||
| 263 | // Remove all non-needed event listeners | |
| 264 | 1 | ConnectionPool.prototype.removeAllEventListeners = function() { |
| 265 | 0 | this.removeAllListeners("close"); |
| 266 | 0 | this.removeAllListeners("error"); |
| 267 | 0 | this.removeAllListeners("timeout"); |
| 268 | 0 | this.removeAllListeners("connect"); |
| 269 | 0 | this.removeAllListeners("end"); |
| 270 | 0 | this.removeAllListeners("parseError"); |
| 271 | 0 | this.removeAllListeners("message"); |
| 272 | 0 | this.removeAllListeners("poolReady"); |
| 273 | } | |
| 274 | ||
| 275 | ||
| 276 | ||
| 277 | ||
| 278 | ||
| 279 | ||
| 280 | ||
| 281 | ||
| 282 | ||
| 283 | ||
| 284 | ||
| 285 | ||
| 286 | ||
| 287 | ||
| 288 | ||
| 289 | ||
| 290 | ||
| 291 | ||
| 292 | ||
| 293 | ||
| 294 | ||
| 295 | ||
| 296 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | exports.setIntegerParameter = function(object, field, defaultValue) { |
| 2 | 0 | if(object[field] == null) { |
| 3 | 0 | object[field] = defaultValue; |
| 4 | 0 | } else if(typeof object[field] !== "number" && object[field] !== parseInt(object[field], 10)) { |
| 5 | 0 | throw "object field [" + field + "] must be a numeric integer value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 6 | } | |
| 7 | } | |
| 8 | ||
| 9 | 1 | exports.setBooleanParameter = function(object, field, defaultValue) { |
| 10 | 0 | if(object[field] == null) { |
| 11 | 0 | object[field] = defaultValue; |
| 12 | 0 | } else if(typeof object[field] !== "boolean") { |
| 13 | 0 | throw "object field [" + field + "] must be a boolean value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | 1 | exports.setStringParameter = function(object, field, defaultValue) { |
| 18 | 0 | if(object[field] == null) { |
| 19 | 0 | object[field] = defaultValue; |
| 20 | 0 | } else if(typeof object[field] !== "string") { |
| 21 | 0 | throw "object field [" + field + "] must be a string value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 22 | } | |
| 23 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('./read_preference').ReadPreference |
| 2 | , Base = require('./base').Base | |
| 3 | , Server = require('./server').Server | |
| 4 | , format = require('util').format | |
| 5 | , timers = require('timers') | |
| 6 | , utils = require('../utils') | |
| 7 | , inherits = require('util').inherits; | |
| 8 | ||
| 9 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 10 | 1 | var processor = require('../utils').processor(); |
| 11 | ||
| 12 | /** | |
| 13 | * Mongos constructor provides a connection to a mongos proxy including failover to additional servers | |
| 14 | * | |
| 15 | * Options | |
| 16 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 17 | * - **ha** {Boolean, default:true}, turn on high availability, attempts to reconnect to down proxies | |
| 18 | * - **haInterval** {Number, default:2000}, time between each replicaset status check. | |
| 19 | * | |
| 20 | * @class Represents a Mongos connection with failover to backup proxies | |
| 21 | * @param {Array} list of mongos server objects | |
| 22 | * @param {Object} [options] additional options for the mongos connection | |
| 23 | */ | |
| 24 | 1 | var Mongos = function Mongos(servers, options) { |
| 25 | // Set up basic | |
| 26 | 0 | if(!(this instanceof Mongos)) |
| 27 | 0 | return new Mongos(servers, options); |
| 28 | ||
| 29 | // Set up event emitter | |
| 30 | 0 | Base.call(this); |
| 31 | ||
| 32 | // Throw error on wrong setup | |
| 33 | 0 | if(servers == null || !Array.isArray(servers) || servers.length == 0) |
| 34 | 0 | throw new Error("At least one mongos proxy must be in the array"); |
| 35 | ||
| 36 | // Ensure we have at least an empty options object | |
| 37 | 0 | this.options = options == null ? {} : options; |
| 38 | // Set default connection pool options | |
| 39 | 0 | this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; |
| 40 | // Enabled ha | |
| 41 | 0 | this.haEnabled = this.options['ha'] == null ? true : this.options['ha']; |
| 42 | 0 | this._haInProgress = false; |
| 43 | // How often are we checking for new servers in the replicaset | |
| 44 | 0 | this.mongosStatusCheckInterval = this.options['haInterval'] == null ? 1000 : this.options['haInterval']; |
| 45 | // Save all the server connections | |
| 46 | 0 | this.servers = servers; |
| 47 | // Servers we need to attempt reconnect with | |
| 48 | 0 | this.downServers = {}; |
| 49 | // Servers that are up | |
| 50 | 0 | this.upServers = {}; |
| 51 | // Up servers by ping time | |
| 52 | 0 | this.upServersByUpTime = {}; |
| 53 | // Emit open setup | |
| 54 | 0 | this.emitOpen = this.options.emitOpen || true; |
| 55 | // Just contains the current lowest ping time and server | |
| 56 | 0 | this.lowestPingTimeServer = null; |
| 57 | 0 | this.lowestPingTime = 0; |
| 58 | // Connection timeout | |
| 59 | 0 | this._connectTimeoutMS = this.socketOptions.connectTimeoutMS |
| 60 | ? this.socketOptions.connectTimeoutMS | |
| 61 | : 1000; | |
| 62 | ||
| 63 | // Add options to servers | |
| 64 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 65 | 0 | var server = this.servers[i]; |
| 66 | 0 | server._callBackStore = this._callBackStore; |
| 67 | 0 | server.auto_reconnect = false; |
| 68 | // Default empty socket options object | |
| 69 | 0 | var socketOptions = {host: server.host, port: server.port}; |
| 70 | // If a socket option object exists clone it | |
| 71 | 0 | if(this.socketOptions != null) { |
| 72 | 0 | var keys = Object.keys(this.socketOptions); |
| 73 | 0 | for(var k = 0; k < keys.length;k++) socketOptions[keys[i]] = this.socketOptions[keys[i]]; |
| 74 | } | |
| 75 | ||
| 76 | // Set socket options | |
| 77 | 0 | server.socketOptions = socketOptions; |
| 78 | } | |
| 79 | ||
| 80 | // Allow setting the socketTimeoutMS on all connections | |
| 81 | // to work around issues such as secondaries blocking due to compaction | |
| 82 | 0 | utils.setSocketTimeoutProperty(this, this.socketOptions); |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * @ignore | |
| 87 | */ | |
| 88 | 1 | inherits(Mongos, Base); |
| 89 | ||
| 90 | /** | |
| 91 | * @ignore | |
| 92 | */ | |
| 93 | 1 | Mongos.prototype.isMongos = function() { |
| 94 | 0 | return true; |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * @ignore | |
| 99 | */ | |
| 100 | 1 | Mongos.prototype.connect = function(db, options, callback) { |
| 101 | 0 | if('function' === typeof options) callback = options, options = {}; |
| 102 | 0 | if(options == null) options = {}; |
| 103 | 0 | if(!('function' === typeof callback)) callback = null; |
| 104 | 0 | var self = this; |
| 105 | ||
| 106 | // Keep reference to parent | |
| 107 | 0 | this.db = db; |
| 108 | // Set server state to connecting | |
| 109 | 0 | this._serverState = 'connecting'; |
| 110 | // Number of total servers that need to initialized (known servers) | |
| 111 | 0 | this._numberOfServersLeftToInitialize = this.servers.length; |
| 112 | // Connect handler | |
| 113 | 0 | var connectHandler = function(_server) { |
| 114 | 0 | return function(err, result) { |
| 115 | 0 | self._numberOfServersLeftToInitialize = self._numberOfServersLeftToInitialize - 1; |
| 116 | ||
| 117 | // Add the server to the list of servers that are up | |
| 118 | 0 | if(!err) { |
| 119 | 0 | self.upServers[format("%s:%s", _server.host, _server.port)] = _server; |
| 120 | } | |
| 121 | ||
| 122 | // We are done connecting | |
| 123 | 0 | if(self._numberOfServersLeftToInitialize == 0) { |
| 124 | // Start ha function if it exists | |
| 125 | 0 | if(self.haEnabled) { |
| 126 | // Setup the ha process | |
| 127 | 0 | if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId); |
| 128 | 0 | self._replicasetTimeoutId = setInterval(self.mongosCheckFunction, self.mongosStatusCheckInterval); |
| 129 | } | |
| 130 | ||
| 131 | // Set the mongos to connected | |
| 132 | 0 | self._serverState = "connected"; |
| 133 | ||
| 134 | // Emit the open event | |
| 135 | 0 | if(self.emitOpen) |
| 136 | 0 | self._emitAcrossAllDbInstances(self, null, "open", null, null, null); |
| 137 | ||
| 138 | 0 | self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null); |
| 139 | // Callback | |
| 140 | 0 | callback(null, self.db); |
| 141 | } | |
| 142 | } | |
| 143 | }; | |
| 144 | ||
| 145 | // Error handler | |
| 146 | 0 | var errorOrCloseHandler = function(_server) { |
| 147 | 0 | return function(err, result) { |
| 148 | // Emit left event, signaling mongos left the ha | |
| 149 | 0 | self.emit('left', 'mongos', _server); |
| 150 | // Execute all the callbacks with errors | |
| 151 | 0 | self.__executeAllCallbacksWithError(err); |
| 152 | // Check if we have the server | |
| 153 | 0 | var found = false; |
| 154 | ||
| 155 | // Get the server name | |
| 156 | 0 | var server_name = format("%s:%s", _server.host, _server.port); |
| 157 | // Add the downed server | |
| 158 | 0 | self.downServers[server_name] = _server; |
| 159 | // Remove the current server from the list | |
| 160 | 0 | delete self.upServers[server_name]; |
| 161 | ||
| 162 | // Emit close across all the attached db instances | |
| 163 | 0 | if(Object.keys(self.upServers).length == 0) { |
| 164 | 0 | self._emitAcrossAllDbInstances(self, null, "close", new Error("mongos disconnected, no valid proxies contactable over tcp"), null, null); |
| 165 | } | |
| 166 | } | |
| 167 | } | |
| 168 | ||
| 169 | // Mongo function | |
| 170 | 0 | this.mongosCheckFunction = function() { |
| 171 | // Set as not waiting for check event | |
| 172 | 0 | self._haInProgress = true; |
| 173 | ||
| 174 | // Servers down | |
| 175 | 0 | var numberOfServersLeft = Object.keys(self.downServers).length; |
| 176 | ||
| 177 | // Check downed servers | |
| 178 | 0 | if(numberOfServersLeft > 0) { |
| 179 | 0 | for(var name in self.downServers) { |
| 180 | // Pop a downed server | |
| 181 | 0 | var downServer = self.downServers[name]; |
| 182 | // Set up the connection options for a Mongos | |
| 183 | 0 | var options = { |
| 184 | auto_reconnect: false, | |
| 185 | returnIsMasterResults: true, | |
| 186 | slaveOk: true, | |
| 187 | poolSize: downServer.poolSize, | |
| 188 | socketOptions: { | |
| 189 | connectTimeoutMS: self._connectTimeoutMS, | |
| 190 | socketTimeoutMS: self._socketTimeoutMS | |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | // Create a new server object | |
| 195 | 0 | var newServer = new Server(downServer.host, downServer.port, options); |
| 196 | // Setup the connection function | |
| 197 | 0 | var connectFunction = function(_db, _server, _options, _callback) { |
| 198 | 0 | return function() { |
| 199 | // Attempt to connect | |
| 200 | 0 | _server.connect(_db, _options, function(err, result) { |
| 201 | 0 | numberOfServersLeft = numberOfServersLeft - 1; |
| 202 | ||
| 203 | 0 | if(err) { |
| 204 | 0 | return _callback(err, _server); |
| 205 | } else { | |
| 206 | // Set the new server settings | |
| 207 | 0 | _server._callBackStore = self._callBackStore; |
| 208 | ||
| 209 | // Add server event handlers | |
| 210 | 0 | _server.on("close", errorOrCloseHandler(_server)); |
| 211 | 0 | _server.on("timeout", errorOrCloseHandler(_server)); |
| 212 | 0 | _server.on("error", errorOrCloseHandler(_server)); |
| 213 | ||
| 214 | // Get a read connection | |
| 215 | 0 | var _connection = _server.checkoutReader(); |
| 216 | // Get the start time | |
| 217 | 0 | var startTime = new Date().getTime(); |
| 218 | ||
| 219 | // Execute ping command to mark each server with the expected times | |
| 220 | 0 | self.db.command({ping:1} |
| 221 | , {failFast:true, connection:_connection}, function(err, result) { | |
| 222 | // Get the start time | |
| 223 | 0 | var endTime = new Date().getTime(); |
| 224 | // Mark the server with the ping time | |
| 225 | 0 | _server.runtimeStats['pingMs'] = endTime - startTime; |
| 226 | // Execute any waiting reads | |
| 227 | 0 | self._commandsStore.execute_writes(); |
| 228 | 0 | self._commandsStore.execute_queries(); |
| 229 | // Callback | |
| 230 | 0 | return _callback(null, _server); |
| 231 | }); | |
| 232 | } | |
| 233 | }); | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | // Attempt to connect to the database | |
| 238 | 0 | connectFunction(self.db, newServer, options, function(err, _server) { |
| 239 | // If we have an error | |
| 240 | 0 | if(err) { |
| 241 | 0 | self.downServers[format("%s:%s", _server.host, _server.port)] = _server; |
| 242 | } | |
| 243 | ||
| 244 | // Connection function | |
| 245 | 0 | var connectionFunction = function(_auth, _connection, _callback) { |
| 246 | 0 | var pending = _auth.length(); |
| 247 | ||
| 248 | 0 | for(var j = 0; j < pending; j++) { |
| 249 | // Get the auth object | |
| 250 | 0 | var _auth = _auth.get(j); |
| 251 | // Unpack the parameter | |
| 252 | 0 | var username = _auth.username; |
| 253 | 0 | var password = _auth.password; |
| 254 | 0 | var options = { |
| 255 | authMechanism: _auth.authMechanism | |
| 256 | , authSource: _auth.authdb | |
| 257 | , connection: _connection | |
| 258 | }; | |
| 259 | ||
| 260 | // If we have changed the service name | |
| 261 | 0 | if(_auth.gssapiServiceName) |
| 262 | 0 | options.gssapiServiceName = _auth.gssapiServiceName; |
| 263 | ||
| 264 | // Hold any error | |
| 265 | 0 | var _error = null; |
| 266 | // Authenticate against the credentials | |
| 267 | 0 | self.db.authenticate(username, password, options, function(err, result) { |
| 268 | 0 | _error = err != null ? err : _error; |
| 269 | // Adjust the pending authentication | |
| 270 | 0 | pending = pending - 1; |
| 271 | // Finished up | |
| 272 | 0 | if(pending == 0) _callback(_error ? _error : null, _error ? false : true); |
| 273 | }); | |
| 274 | } | |
| 275 | } | |
| 276 | ||
| 277 | // Run auths against the connections | |
| 278 | 0 | if(self.auth.length() > 0) { |
| 279 | 0 | var connections = _server.allRawConnections(); |
| 280 | 0 | var pendingAuthConn = connections.length; |
| 281 | ||
| 282 | // No connections we are done | |
| 283 | 0 | if(connections.length == 0) { |
| 284 | // Set ha done | |
| 285 | 0 | if(numberOfServersLeft == 0) { |
| 286 | 0 | self._haInProgress = false; |
| 287 | } | |
| 288 | } | |
| 289 | ||
| 290 | // Final error object | |
| 291 | 0 | var finalError = null; |
| 292 | // Go over all the connections | |
| 293 | 0 | for(var j = 0; j < connections.length; j++) { |
| 294 | ||
| 295 | // Execute against all the connections | |
| 296 | 0 | connectionFunction(self.auth, connections[j], function(err, result) { |
| 297 | // Pending authentication | |
| 298 | 0 | pendingAuthConn = pendingAuthConn - 1 ; |
| 299 | ||
| 300 | // Save error if any | |
| 301 | 0 | finalError = err ? err : finalError; |
| 302 | ||
| 303 | // If we are done let's finish up | |
| 304 | 0 | if(pendingAuthConn == 0) { |
| 305 | // Set ha done | |
| 306 | 0 | if(numberOfServersLeft == 0) { |
| 307 | 0 | self._haInProgress = false; |
| 308 | } | |
| 309 | ||
| 310 | 0 | if(!err) { |
| 311 | 0 | add_server(self, _server); |
| 312 | } | |
| 313 | ||
| 314 | // Execute any waiting reads | |
| 315 | 0 | self._commandsStore.execute_writes(); |
| 316 | 0 | self._commandsStore.execute_queries(); |
| 317 | } | |
| 318 | }); | |
| 319 | } | |
| 320 | } else { | |
| 321 | 0 | if(!err) { |
| 322 | 0 | add_server(self, _server); |
| 323 | } | |
| 324 | ||
| 325 | // Set ha done | |
| 326 | 0 | if(numberOfServersLeft == 0) { |
| 327 | 0 | self._haInProgress = false; |
| 328 | // Execute any waiting reads | |
| 329 | 0 | self._commandsStore.execute_writes(); |
| 330 | 0 | self._commandsStore.execute_queries(); |
| 331 | } | |
| 332 | } | |
| 333 | })(); | |
| 334 | } | |
| 335 | } else { | |
| 336 | 0 | self._haInProgress = false; |
| 337 | } | |
| 338 | } | |
| 339 | ||
| 340 | // Connect all the server instances | |
| 341 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 342 | // Get the connection | |
| 343 | 0 | var server = this.servers[i]; |
| 344 | 0 | server.mongosInstance = this; |
| 345 | // Add server event handlers | |
| 346 | 0 | server.on("close", errorOrCloseHandler(server)); |
| 347 | 0 | server.on("timeout", errorOrCloseHandler(server)); |
| 348 | 0 | server.on("error", errorOrCloseHandler(server)); |
| 349 | ||
| 350 | // Configuration | |
| 351 | 0 | var options = { |
| 352 | slaveOk: true, | |
| 353 | poolSize: server.poolSize, | |
| 354 | socketOptions: { connectTimeoutMS: self._connectTimeoutMS }, | |
| 355 | returnIsMasterResults: true | |
| 356 | } | |
| 357 | ||
| 358 | // Connect the instance | |
| 359 | 0 | server.connect(self.db, options, connectHandler(server)); |
| 360 | } | |
| 361 | } | |
| 362 | ||
| 363 | /** | |
| 364 | * @ignore | |
| 365 | * Add a server to the list of up servers and sort them by ping time | |
| 366 | */ | |
| 367 | 1 | var add_server = function(self, _server) { |
| 368 | // Emit a new server joined | |
| 369 | 0 | self.emit('joined', "mongos", null, _server); |
| 370 | // Get the server url | |
| 371 | 0 | var server_key = format("%s:%s", _server.host, _server.port); |
| 372 | // Push to list of valid server | |
| 373 | 0 | self.upServers[server_key] = _server; |
| 374 | // Remove the server from the list of downed servers | |
| 375 | 0 | delete self.downServers[server_key]; |
| 376 | ||
| 377 | // Sort the keys by ping time | |
| 378 | 0 | var keys = Object.keys(self.upServers); |
| 379 | 0 | var _upServersSorted = {}; |
| 380 | 0 | var _upServers = [] |
| 381 | ||
| 382 | // Get all the servers | |
| 383 | 0 | for(var name in self.upServers) { |
| 384 | 0 | _upServers.push(self.upServers[name]); |
| 385 | } | |
| 386 | ||
| 387 | // Sort all the server | |
| 388 | 0 | _upServers.sort(function(a, b) { |
| 389 | 0 | return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs']; |
| 390 | }); | |
| 391 | ||
| 392 | // Rebuild the upServer | |
| 393 | 0 | for(var i = 0; i < _upServers.length; i++) { |
| 394 | 0 | _upServersSorted[format("%s:%s", _upServers[i].host, _upServers[i].port)] = _upServers[i]; |
| 395 | } | |
| 396 | ||
| 397 | // Set the up servers | |
| 398 | 0 | self.upServers = _upServersSorted; |
| 399 | } | |
| 400 | ||
| 401 | /** | |
| 402 | * @ignore | |
| 403 | * Just return the currently picked active connection | |
| 404 | */ | |
| 405 | 1 | Mongos.prototype.allServerInstances = function() { |
| 406 | 0 | return this.servers; |
| 407 | } | |
| 408 | ||
| 409 | /** | |
| 410 | * Always ourselves | |
| 411 | * @ignore | |
| 412 | */ | |
| 413 | 1 | Mongos.prototype.setReadPreference = function() {} |
| 414 | ||
| 415 | /** | |
| 416 | * @ignore | |
| 417 | */ | |
| 418 | 1 | Mongos.prototype.allRawConnections = function() { |
| 419 | // Neeed to build a complete list of all raw connections, start with master server | |
| 420 | 0 | var allConnections = []; |
| 421 | // Get all connected connections | |
| 422 | 0 | for(var name in this.upServers) { |
| 423 | 0 | allConnections = allConnections.concat(this.upServers[name].allRawConnections()); |
| 424 | } | |
| 425 | // Return all the conections | |
| 426 | 0 | return allConnections; |
| 427 | } | |
| 428 | ||
| 429 | /** | |
| 430 | * @ignore | |
| 431 | */ | |
| 432 | 1 | Mongos.prototype.isConnected = function() { |
| 433 | 0 | return Object.keys(this.upServers).length > 0; |
| 434 | } | |
| 435 | ||
| 436 | /** | |
| 437 | * @ignore | |
| 438 | */ | |
| 439 | 1 | Mongos.prototype.isAutoReconnect = function() { |
| 440 | 0 | return true; |
| 441 | } | |
| 442 | ||
| 443 | /** | |
| 444 | * @ignore | |
| 445 | */ | |
| 446 | 1 | Mongos.prototype.canWrite = Mongos.prototype.isConnected; |
| 447 | ||
| 448 | /** | |
| 449 | * @ignore | |
| 450 | */ | |
| 451 | 1 | Mongos.prototype.canRead = Mongos.prototype.isConnected; |
| 452 | ||
| 453 | /** | |
| 454 | * @ignore | |
| 455 | */ | |
| 456 | 1 | Mongos.prototype.isDestroyed = function() { |
| 457 | 0 | return this._serverState == 'destroyed'; |
| 458 | } | |
| 459 | ||
| 460 | /** | |
| 461 | * @ignore | |
| 462 | */ | |
| 463 | 1 | Mongos.prototype.checkoutWriter = function() { |
| 464 | // Checkout a writer | |
| 465 | 0 | var keys = Object.keys(this.upServers); |
| 466 | // console.dir("============================ checkoutWriter :: " + keys.length) | |
| 467 | 0 | if(keys.length == 0) return null; |
| 468 | // console.log("=============== checkoutWriter :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port) | |
| 469 | 0 | return this.upServers[keys[0]].checkoutWriter(); |
| 470 | } | |
| 471 | ||
| 472 | /** | |
| 473 | * @ignore | |
| 474 | */ | |
| 475 | 1 | Mongos.prototype.checkoutReader = function(read) { |
| 476 | // console.log("=============== checkoutReader :: read :: " + read); | |
| 477 | // If read is set to null default to primary | |
| 478 | 0 | read = read || 'primary' |
| 479 | // If we have a read preference object unpack it | |
| 480 | 0 | if(read != null && typeof read == 'object' && read['_type'] == 'ReadPreference') { |
| 481 | // Validate if the object is using a valid mode | |
| 482 | 0 | if(!read.isValid()) throw new Error("Illegal readPreference mode specified, " + read.mode); |
| 483 | 0 | } else if(!ReadPreference.isValid(read)) { |
| 484 | 0 | throw new Error("Illegal readPreference mode specified, " + read); |
| 485 | } | |
| 486 | ||
| 487 | // Checkout a writer | |
| 488 | 0 | var keys = Object.keys(this.upServers); |
| 489 | 0 | if(keys.length == 0) return null; |
| 490 | // console.log("=============== checkoutReader :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port) | |
| 491 | // console.dir(this._commandsStore.commands) | |
| 492 | 0 | return this.upServers[keys[0]].checkoutWriter(); |
| 493 | } | |
| 494 | ||
| 495 | /** | |
| 496 | * @ignore | |
| 497 | */ | |
| 498 | 1 | Mongos.prototype.close = function(callback) { |
| 499 | 0 | var self = this; |
| 500 | // Set server status as disconnected | |
| 501 | 0 | this._serverState = 'destroyed'; |
| 502 | // Number of connections to close | |
| 503 | 0 | var numberOfConnectionsToClose = self.servers.length; |
| 504 | // If we have a ha process running kill it | |
| 505 | 0 | if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId); |
| 506 | 0 | self._replicasetTimeoutId = null; |
| 507 | ||
| 508 | // Emit close event | |
| 509 | 0 | processor(function() { |
| 510 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 511 | }); | |
| 512 | ||
| 513 | // Flush out any remaining call handlers | |
| 514 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 515 | ||
| 516 | // Close all the up servers | |
| 517 | 0 | for(var name in this.upServers) { |
| 518 | 0 | this.upServers[name].close(function(err, result) { |
| 519 | 0 | numberOfConnectionsToClose = numberOfConnectionsToClose - 1; |
| 520 | ||
| 521 | // Callback if we have one defined | |
| 522 | 0 | if(numberOfConnectionsToClose == 0 && typeof callback == 'function') { |
| 523 | 0 | callback(null); |
| 524 | } | |
| 525 | }); | |
| 526 | } | |
| 527 | } | |
| 528 | ||
| 529 | /** | |
| 530 | * @ignore | |
| 531 | * Return the used state | |
| 532 | */ | |
| 533 | 1 | Mongos.prototype._isUsed = function() { |
| 534 | 0 | return this._used; |
| 535 | } | |
| 536 | ||
| 537 | 1 | exports.Mongos = Mongos; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the Read Preference. | |
| 3 | * | |
| 4 | * Read Preferences | |
| 5 | * - **ReadPreference.PRIMARY**, Read from primary only. All operations produce an error (throw an exception where applicable) if primary is unavailable. Cannot be combined with tags (This is the default.). | |
| 6 | * - **ReadPreference.PRIMARY_PREFERRED**, Read from primary if available, otherwise a secondary. | |
| 7 | * - **ReadPreference.SECONDARY**, Read from secondary if available, otherwise error. | |
| 8 | * - **ReadPreference.SECONDARY_PREFERRED**, Read from a secondary if available, otherwise read from the primary. | |
| 9 | * - **ReadPreference.NEAREST**, All modes read from among the nearest candidates, but unlike other modes, NEAREST will include both the primary and all secondaries in the random selection. | |
| 10 | * | |
| 11 | * @class Represents a Read Preference. | |
| 12 | * @param {String} the read preference type | |
| 13 | * @param {Object} tags | |
| 14 | * @return {ReadPreference} | |
| 15 | */ | |
| 16 | 1 | var ReadPreference = function(mode, tags) { |
| 17 | 0 | if(!(this instanceof ReadPreference)) |
| 18 | 0 | return new ReadPreference(mode, tags); |
| 19 | 0 | this._type = 'ReadPreference'; |
| 20 | 0 | this.mode = mode; |
| 21 | 0 | this.tags = tags; |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | */ | |
| 27 | 1 | ReadPreference.isValid = function(_mode) { |
| 28 | 0 | return (_mode == ReadPreference.PRIMARY || _mode == ReadPreference.PRIMARY_PREFERRED |
| 29 | || _mode == ReadPreference.SECONDARY || _mode == ReadPreference.SECONDARY_PREFERRED | |
| 30 | || _mode == ReadPreference.NEAREST | |
| 31 | || _mode == true || _mode == false); | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * @ignore | |
| 36 | */ | |
| 37 | 1 | ReadPreference.prototype.isValid = function(mode) { |
| 38 | 0 | var _mode = typeof mode == 'string' ? mode : this.mode; |
| 39 | 0 | return ReadPreference.isValid(_mode); |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * @ignore | |
| 44 | */ | |
| 45 | 1 | ReadPreference.prototype.toObject = function() { |
| 46 | 0 | var object = {mode:this.mode}; |
| 47 | ||
| 48 | 0 | if(this.tags != null) { |
| 49 | 0 | object['tags'] = this.tags; |
| 50 | } | |
| 51 | ||
| 52 | 0 | return object; |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * @ignore | |
| 57 | */ | |
| 58 | 1 | ReadPreference.PRIMARY = 'primary'; |
| 59 | 1 | ReadPreference.PRIMARY_PREFERRED = 'primaryPreferred'; |
| 60 | 1 | ReadPreference.SECONDARY = 'secondary'; |
| 61 | 1 | ReadPreference.SECONDARY_PREFERRED = 'secondaryPreferred'; |
| 62 | 1 | ReadPreference.NEAREST = 'nearest' |
| 63 | ||
| 64 | /** | |
| 65 | * @ignore | |
| 66 | */ | |
| 67 | 1 | exports.ReadPreference = ReadPreference; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../../commands/db_command').DbCommand |
| 2 | , format = require('util').format; | |
| 3 | ||
| 4 | 1 | var HighAvailabilityProcess = function(replset, options) { |
| 5 | 0 | this.replset = replset; |
| 6 | 0 | this.options = options; |
| 7 | 0 | this.server = null; |
| 8 | 0 | this.state = HighAvailabilityProcess.INIT; |
| 9 | 0 | this.selectedIndex = 0; |
| 10 | } | |
| 11 | ||
| 12 | 1 | HighAvailabilityProcess.INIT = 'init'; |
| 13 | 1 | HighAvailabilityProcess.RUNNING = 'running'; |
| 14 | 1 | HighAvailabilityProcess.STOPPED = 'stopped'; |
| 15 | ||
| 16 | 1 | HighAvailabilityProcess.prototype.start = function() { |
| 17 | 0 | var self = this; |
| 18 | 0 | if(this.replset._state |
| 19 | && Object.keys(this.replset._state.addresses).length == 0) { | |
| 20 | 0 | if(this.server) this.server.close(); |
| 21 | 0 | this.state = HighAvailabilityProcess.STOPPED; |
| 22 | 0 | return; |
| 23 | } | |
| 24 | ||
| 25 | 0 | if(this.server) this.server.close(); |
| 26 | // Start the running | |
| 27 | 0 | this._haProcessInProcess = false; |
| 28 | 0 | this.state = HighAvailabilityProcess.RUNNING; |
| 29 | ||
| 30 | // Get all possible reader servers | |
| 31 | 0 | var candidate_servers = this.replset._state.getAllReadServers(); |
| 32 | 0 | if(candidate_servers.length == 0) { |
| 33 | 0 | return; |
| 34 | } | |
| 35 | ||
| 36 | // Select a candidate server for the connection | |
| 37 | 0 | var server = candidate_servers[this.selectedIndex % candidate_servers.length]; |
| 38 | 0 | this.selectedIndex = this.selectedIndex + 1; |
| 39 | ||
| 40 | // Unpack connection options | |
| 41 | 0 | var connectTimeoutMS = self.options.connectTimeoutMS || 10000; |
| 42 | 0 | var socketTimeoutMS = self.options.socketTimeoutMS || 30000; |
| 43 | ||
| 44 | // Just ensure we don't have a full cycle dependency | |
| 45 | 0 | var Db = require('../../db').Db |
| 46 | 0 | var Server = require('../server').Server; |
| 47 | ||
| 48 | // Set up a new server instance | |
| 49 | 0 | var newServer = new Server(server.host, server.port, { |
| 50 | auto_reconnect: false | |
| 51 | , returnIsMasterResults: true | |
| 52 | , poolSize: 1 | |
| 53 | , socketOptions: { | |
| 54 | connectTimeoutMS: connectTimeoutMS, | |
| 55 | socketTimeoutMS: socketTimeoutMS, | |
| 56 | keepAlive: 100 | |
| 57 | } | |
| 58 | , ssl: this.options.ssl | |
| 59 | , sslValidate: this.options.sslValidate | |
| 60 | , sslCA: this.options.sslCA | |
| 61 | , sslCert: this.options.sslCert | |
| 62 | , sslKey: this.options.sslKey | |
| 63 | , sslPass: this.options.sslPass | |
| 64 | }); | |
| 65 | ||
| 66 | // Create new dummy db for app | |
| 67 | 0 | self.db = new Db('local', newServer, {w:1}); |
| 68 | ||
| 69 | // Set up the event listeners | |
| 70 | 0 | newServer.once("error", _handle(this, newServer)); |
| 71 | 0 | newServer.once("close", _handle(this, newServer)); |
| 72 | 0 | newServer.once("timeout", _handle(this, newServer)); |
| 73 | 0 | newServer.name = format("%s:%s", server.host, server.port); |
| 74 | ||
| 75 | // Let's attempt a connection over here | |
| 76 | 0 | newServer.connect(self.db, function(err, result, _server) { |
| 77 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 78 | 0 | _server.close(); |
| 79 | } | |
| 80 | ||
| 81 | 0 | if(err) { |
| 82 | // Close the server | |
| 83 | 0 | _server.close(); |
| 84 | // Check if we can even do HA (is there anything running) | |
| 85 | 0 | if(Object.keys(self.replset._state.addresses).length == 0) { |
| 86 | 0 | return; |
| 87 | } | |
| 88 | ||
| 89 | // Let's boot the ha timeout settings | |
| 90 | 0 | setTimeout(function() { |
| 91 | 0 | self.start(); |
| 92 | }, self.options.haInterval); | |
| 93 | } else { | |
| 94 | 0 | self.server = _server; |
| 95 | // Let's boot the ha timeout settings | |
| 96 | 0 | setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 97 | } | |
| 98 | }); | |
| 99 | } | |
| 100 | ||
| 101 | 1 | HighAvailabilityProcess.prototype.stop = function() { |
| 102 | 0 | this.state = HighAvailabilityProcess.STOPPED; |
| 103 | 0 | if(this.server) this.server.close(); |
| 104 | } | |
| 105 | ||
| 106 | 1 | var _timeoutHandle = function(self) { |
| 107 | 0 | return function() { |
| 108 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 109 | // Stop all server instances | |
| 110 | 0 | for(var name in self.replset._state.addresses) { |
| 111 | 0 | self.replset._state.addresses[name].close(); |
| 112 | 0 | delete self.replset._state.addresses[name]; |
| 113 | } | |
| 114 | ||
| 115 | // Finished pinging | |
| 116 | 0 | return; |
| 117 | } | |
| 118 | ||
| 119 | // If the server is connected | |
| 120 | 0 | if(self.server.isConnected() && !self._haProcessInProcess) { |
| 121 | // Start HA process | |
| 122 | 0 | self._haProcessInProcess = true; |
| 123 | // Execute is master command | |
| 124 | 0 | self.db._executeQueryCommand(DbCommand.createIsMasterCommand(self.db), |
| 125 | {failFast:true, connection: self.server.checkoutReader()} | |
| 126 | , function(err, res) { | |
| 127 | 0 | if(err) { |
| 128 | 0 | self.server.close(); |
| 129 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 130 | } | |
| 131 | ||
| 132 | // Master document | |
| 133 | 0 | var master = res.documents[0]; |
| 134 | 0 | var hosts = master.hosts || []; |
| 135 | 0 | var reconnect_servers = []; |
| 136 | 0 | var state = self.replset._state; |
| 137 | ||
| 138 | // We are in recovery mode, let's remove the current server | |
| 139 | 0 | if(!master.ismaster |
| 140 | && !master.secondary | |
| 141 | && state.addresses[master.me]) { | |
| 142 | 0 | self.server.close(); |
| 143 | 0 | state.addresses[master.me].close(); |
| 144 | 0 | delete state.secondaries[master.me]; |
| 145 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 146 | } | |
| 147 | ||
| 148 | // For all the hosts let's check that we have connections | |
| 149 | 0 | for(var i = 0; i < hosts.length; i++) { |
| 150 | 0 | var host = hosts[i]; |
| 151 | // Check if we need to reconnect to a server | |
| 152 | 0 | if(state.addresses[host] == null) { |
| 153 | 0 | reconnect_servers.push(host); |
| 154 | 0 | } else if(state.addresses[host] && !state.addresses[host].isConnected()) { |
| 155 | 0 | state.addresses[host].close(); |
| 156 | 0 | delete state.secondaries[host]; |
| 157 | 0 | reconnect_servers.push(host); |
| 158 | } | |
| 159 | ||
| 160 | 0 | if((master.primary && state.master == null) |
| 161 | || (master.primary && state.master.name != master.primary)) { | |
| 162 | ||
| 163 | // Locate the primary and set it | |
| 164 | 0 | if(state.addresses[master.primary]) { |
| 165 | 0 | if(state.master) state.master.close(); |
| 166 | 0 | delete state.secondaries[master.primary]; |
| 167 | 0 | state.master = state.addresses[master.primary]; |
| 168 | } | |
| 169 | ||
| 170 | // Set up the changes | |
| 171 | 0 | if(state.master != null && state.master.isMasterDoc != null) { |
| 172 | 0 | state.master.isMasterDoc.ismaster = true; |
| 173 | 0 | state.master.isMasterDoc.secondary = false; |
| 174 | 0 | } else if(state.master != null) { |
| 175 | 0 | state.master.isMasterDoc = master; |
| 176 | 0 | state.master.isMasterDoc.ismaster = true; |
| 177 | 0 | state.master.isMasterDoc.secondary = false; |
| 178 | } | |
| 179 | ||
| 180 | // Execute any waiting commands (queries or writes) | |
| 181 | 0 | self.replset._commandsStore.execute_queries(); |
| 182 | 0 | self.replset._commandsStore.execute_writes(); |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | // Let's reconnect to any server needed | |
| 187 | 0 | if(reconnect_servers.length > 0) { |
| 188 | 0 | _reconnect_servers(self, reconnect_servers); |
| 189 | } else { | |
| 190 | 0 | self._haProcessInProcess = false |
| 191 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 192 | } | |
| 193 | }); | |
| 194 | 0 | } else if(!self.server.isConnected()) { |
| 195 | 0 | setTimeout(function() { |
| 196 | 0 | return self.start(); |
| 197 | }, self.options.haInterval); | |
| 198 | } else { | |
| 199 | 0 | setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 200 | } | |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | 1 | var _reconnect_servers = function(self, reconnect_servers) { |
| 205 | 0 | if(reconnect_servers.length == 0) { |
| 206 | 0 | self._haProcessInProcess = false |
| 207 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 208 | } | |
| 209 | ||
| 210 | // Unpack connection options | |
| 211 | 0 | var connectTimeoutMS = self.options.connectTimeoutMS || 10000; |
| 212 | 0 | var socketTimeoutMS = self.options.socketTimeoutMS || 0; |
| 213 | ||
| 214 | // Server class | |
| 215 | 0 | var Db = require('../../db').Db |
| 216 | 0 | var Server = require('../server').Server; |
| 217 | // Get the host | |
| 218 | 0 | var host = reconnect_servers.shift(); |
| 219 | // Split it up | |
| 220 | 0 | var _host = host.split(":")[0]; |
| 221 | 0 | var _port = parseInt(host.split(":")[1], 10); |
| 222 | ||
| 223 | // Set up a new server instance | |
| 224 | 0 | var newServer = new Server(_host, _port, { |
| 225 | auto_reconnect: false | |
| 226 | , returnIsMasterResults: true | |
| 227 | , poolSize: self.options.poolSize | |
| 228 | , socketOptions: { | |
| 229 | connectTimeoutMS: connectTimeoutMS, | |
| 230 | socketTimeoutMS: socketTimeoutMS | |
| 231 | } | |
| 232 | , ssl: self.options.ssl | |
| 233 | , sslValidate: self.options.sslValidate | |
| 234 | , sslCA: self.options.sslCA | |
| 235 | , sslCert: self.options.sslCert | |
| 236 | , sslKey: self.options.sslKey | |
| 237 | , sslPass: self.options.sslPass | |
| 238 | }); | |
| 239 | ||
| 240 | // Create new dummy db for app | |
| 241 | 0 | var db = new Db('local', newServer, {w:1}); |
| 242 | 0 | var state = self.replset._state; |
| 243 | ||
| 244 | // Set up the event listeners | |
| 245 | 0 | newServer.once("error", _repl_set_handler("error", self.replset, newServer)); |
| 246 | 0 | newServer.once("close", _repl_set_handler("close", self.replset, newServer)); |
| 247 | 0 | newServer.once("timeout", _repl_set_handler("timeout", self.replset, newServer)); |
| 248 | ||
| 249 | // Set shared state | |
| 250 | 0 | newServer.name = host; |
| 251 | 0 | newServer._callBackStore = self.replset._callBackStore; |
| 252 | 0 | newServer.replicasetInstance = self.replset; |
| 253 | 0 | newServer.enableRecordQueryStats(self.replset.recordQueryStats); |
| 254 | ||
| 255 | // Let's attempt a connection over here | |
| 256 | 0 | newServer.connect(db, function(err, result, _server) { |
| 257 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 258 | 0 | _server.close(); |
| 259 | } | |
| 260 | ||
| 261 | // If we connected let's check what kind of server we have | |
| 262 | 0 | if(!err) { |
| 263 | 0 | _apply_auths(self, db, _server, function(err, result) { |
| 264 | 0 | if(err) { |
| 265 | 0 | _server.close(); |
| 266 | // Process the next server | |
| 267 | 0 | return setTimeout(function() { |
| 268 | 0 | _reconnect_servers(self, reconnect_servers); |
| 269 | }, self.options.haInterval); | |
| 270 | } | |
| 271 | 0 | var doc = _server.isMasterDoc; |
| 272 | // Fire error on any unknown callbacks for this server | |
| 273 | 0 | self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err); |
| 274 | ||
| 275 | 0 | if(doc.ismaster) { |
| 276 | // Emit primary added | |
| 277 | 0 | self.replset.emit('joined', "primary", doc, _server); |
| 278 | ||
| 279 | // If it was a secondary remove it | |
| 280 | 0 | if(state.secondaries[doc.me]) { |
| 281 | 0 | delete state.secondaries[doc.me]; |
| 282 | } | |
| 283 | ||
| 284 | // Override any server in list of addresses | |
| 285 | 0 | state.addresses[doc.me] = _server; |
| 286 | // Set server as master | |
| 287 | 0 | state.master = _server; |
| 288 | // Execute any waiting writes | |
| 289 | 0 | self.replset._commandsStore.execute_writes(); |
| 290 | 0 | } else if(doc.secondary) { |
| 291 | // Emit secondary added | |
| 292 | 0 | self.replset.emit('joined', "secondary", doc, _server); |
| 293 | // Add the secondary to the state | |
| 294 | 0 | state.secondaries[doc.me] = _server; |
| 295 | // Override any server in list of addresses | |
| 296 | 0 | state.addresses[doc.me] = _server; |
| 297 | // Execute any waiting reads | |
| 298 | 0 | self.replset._commandsStore.execute_queries(); |
| 299 | } else { | |
| 300 | 0 | _server.close(); |
| 301 | } | |
| 302 | ||
| 303 | // Set any tags on the instance server | |
| 304 | 0 | _server.name = doc.me; |
| 305 | 0 | _server.tags = doc.tags; |
| 306 | // Process the next server | |
| 307 | 0 | setTimeout(function() { |
| 308 | 0 | _reconnect_servers(self, reconnect_servers); |
| 309 | }, self.options.haInterval); | |
| 310 | }); | |
| 311 | } else { | |
| 312 | 0 | _server.close(); |
| 313 | 0 | self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err); |
| 314 | ||
| 315 | 0 | setTimeout(function() { |
| 316 | 0 | _reconnect_servers(self, reconnect_servers); |
| 317 | }, self.options.haInterval); | |
| 318 | } | |
| 319 | }); | |
| 320 | } | |
| 321 | ||
| 322 | 1 | var _apply_auths = function(self, _db, _server, _callback) { |
| 323 | 0 | if(self.replset.auth.length() == 0) return _callback(null); |
| 324 | // Apply any authentication needed | |
| 325 | 0 | if(self.replset.auth.length() > 0) { |
| 326 | 0 | var pending = self.replset.auth.length(); |
| 327 | 0 | var connections = _server.allRawConnections(); |
| 328 | 0 | var pendingAuthConn = connections.length; |
| 329 | ||
| 330 | // Connection function | |
| 331 | 0 | var connectionFunction = function(_auth, _connection, __callback) { |
| 332 | 0 | var pending = _auth.length(); |
| 333 | ||
| 334 | 0 | for(var j = 0; j < pending; j++) { |
| 335 | // Get the auth object | |
| 336 | 0 | var _auth = _auth.get(j); |
| 337 | // Unpack the parameter | |
| 338 | 0 | var username = _auth.username; |
| 339 | 0 | var password = _auth.password; |
| 340 | 0 | var options = { |
| 341 | authMechanism: _auth.authMechanism | |
| 342 | , authSource: _auth.authdb | |
| 343 | , connection: _connection | |
| 344 | }; | |
| 345 | ||
| 346 | // If we have changed the service name | |
| 347 | 0 | if(_auth.gssapiServiceName) |
| 348 | 0 | options.gssapiServiceName = _auth.gssapiServiceName; |
| 349 | ||
| 350 | // Hold any error | |
| 351 | 0 | var _error = null; |
| 352 | ||
| 353 | // Authenticate against the credentials | |
| 354 | 0 | _db.authenticate(username, password, options, function(err, result) { |
| 355 | 0 | _error = err != null ? err : _error; |
| 356 | // Adjust the pending authentication | |
| 357 | 0 | pending = pending - 1; |
| 358 | // Finished up | |
| 359 | 0 | if(pending == 0) __callback(_error ? _error : null, _error ? false : true); |
| 360 | }); | |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | // Final error object | |
| 365 | 0 | var finalError = null; |
| 366 | // Iterate over all the connections | |
| 367 | 0 | for(var i = 0; i < connections.length; i++) { |
| 368 | 0 | connectionFunction(self.replset.auth, connections[i], function(err, result) { |
| 369 | // Pending authentication | |
| 370 | 0 | pendingAuthConn = pendingAuthConn - 1 ; |
| 371 | ||
| 372 | // Save error if any | |
| 373 | 0 | finalError = err ? err : finalError; |
| 374 | ||
| 375 | // If we are done let's finish up | |
| 376 | 0 | if(pendingAuthConn == 0) { |
| 377 | 0 | _callback(null); |
| 378 | } | |
| 379 | }); | |
| 380 | } | |
| 381 | } | |
| 382 | } | |
| 383 | ||
| 384 | 1 | var _handle = function(self, server) { |
| 385 | 0 | return function(err) { |
| 386 | 0 | server.close(); |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | 1 | var _repl_set_handler = function(event, self, server) { |
| 391 | 0 | var ReplSet = require('./repl_set').ReplSet; |
| 392 | ||
| 393 | 0 | return function(err, doc) { |
| 394 | 0 | server.close(); |
| 395 | ||
| 396 | // The event happened to a primary | |
| 397 | // Remove it from play | |
| 398 | 0 | if(self._state.isPrimary(server)) { |
| 399 | 0 | self._state.master == null; |
| 400 | 0 | self._serverState = ReplSet.REPLSET_READ_ONLY; |
| 401 | 0 | } else if(self._state.isSecondary(server)) { |
| 402 | 0 | delete self._state.secondaries[server.name]; |
| 403 | } | |
| 404 | ||
| 405 | // Unpack variables | |
| 406 | 0 | var host = server.socketOptions.host; |
| 407 | 0 | var port = server.socketOptions.port; |
| 408 | ||
| 409 | // Fire error on any unknown callbacks | |
| 410 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 411 | } | |
| 412 | } | |
| 413 | ||
| 414 | 1 | exports.HighAvailabilityProcess = HighAvailabilityProcess; |
| 415 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var PingStrategy = require('./strategies/ping_strategy').PingStrategy |
| 2 | , StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy | |
| 3 | , ReadPreference = require('../read_preference').ReadPreference; | |
| 4 | ||
| 5 | 1 | var Options = function(options) { |
| 6 | 0 | options = options || {}; |
| 7 | 0 | this._options = options; |
| 8 | 0 | this.ha = options.ha || true; |
| 9 | 0 | this.haInterval = options.haInterval || 2000; |
| 10 | 0 | this.reconnectWait = options.reconnectWait || 1000; |
| 11 | 0 | this.retries = options.retries || 30; |
| 12 | 0 | this.rs_name = options.rs_name; |
| 13 | 0 | this.socketOptions = options.socketOptions || {}; |
| 14 | 0 | this.readPreference = options.readPreference; |
| 15 | 0 | this.readSecondary = options.read_secondary; |
| 16 | 0 | this.poolSize = options.poolSize == null ? 5 : options.poolSize; |
| 17 | 0 | this.strategy = options.strategy || 'ping'; |
| 18 | 0 | this.secondaryAcceptableLatencyMS = options.secondaryAcceptableLatencyMS || 15; |
| 19 | 0 | this.connectArbiter = options.connectArbiter || false; |
| 20 | 0 | this.connectWithNoPrimary = options.connectWithNoPrimary || false; |
| 21 | 0 | this.logger = options.logger; |
| 22 | 0 | this.ssl = options.ssl || false; |
| 23 | 0 | this.sslValidate = options.sslValidate || false; |
| 24 | 0 | this.sslCA = options.sslCA; |
| 25 | 0 | this.sslCert = options.sslCert; |
| 26 | 0 | this.sslKey = options.sslKey; |
| 27 | 0 | this.sslPass = options.sslPass; |
| 28 | 0 | this.emitOpen = options.emitOpen || true; |
| 29 | } | |
| 30 | ||
| 31 | 1 | Options.prototype.init = function() { |
| 32 | 0 | if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) { |
| 33 | 0 | throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate"); |
| 34 | } | |
| 35 | ||
| 36 | // Make sure strategy is one of the two allowed | |
| 37 | 0 | if(this.strategy != null && (this.strategy != 'ping' && this.strategy != 'statistical' && this.strategy != 'none')) |
| 38 | 0 | throw new Error("Only ping or statistical strategies allowed"); |
| 39 | ||
| 40 | 0 | if(this.strategy == null) this.strategy = 'ping'; |
| 41 | ||
| 42 | // Set logger if strategy exists | |
| 43 | 0 | if(this.strategyInstance) this.strategyInstance.logger = this.logger; |
| 44 | ||
| 45 | // Unpack read Preference | |
| 46 | 0 | var readPreference = this.readPreference; |
| 47 | // Validate correctness of Read preferences | |
| 48 | 0 | if(readPreference != null) { |
| 49 | 0 | if(readPreference != ReadPreference.PRIMARY && readPreference != ReadPreference.PRIMARY_PREFERRED |
| 50 | && readPreference != ReadPreference.SECONDARY && readPreference != ReadPreference.SECONDARY_PREFERRED | |
| 51 | && readPreference != ReadPreference.NEAREST && typeof readPreference != 'object' && readPreference['_type'] != 'ReadPreference') { | |
| 52 | 0 | throw new Error("Illegal readPreference mode specified, " + readPreference); |
| 53 | } | |
| 54 | ||
| 55 | 0 | this.readPreference = readPreference; |
| 56 | } else { | |
| 57 | 0 | this.readPreference = null; |
| 58 | } | |
| 59 | ||
| 60 | // Ensure read_secondary is set correctly | |
| 61 | 0 | if(this.readSecondary != null) |
| 62 | 0 | this.readSecondary = this.readPreference == ReadPreference.PRIMARY |
| 63 | || this.readPreference == false | |
| 64 | || this.readPreference == null ? false : true; | |
| 65 | ||
| 66 | // Ensure correct slave set | |
| 67 | 0 | if(this.readSecondary) this.slaveOk = true; |
| 68 | ||
| 69 | // Set up logger if any set | |
| 70 | 0 | this.logger = this.logger != null |
| 71 | && (typeof this.logger.debug == 'function') | |
| 72 | && (typeof this.logger.error == 'function') | |
| 73 | && (typeof this.logger.debug == 'function') | |
| 74 | ? this.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 75 | ||
| 76 | // Connection timeout | |
| 77 | 0 | this.connectTimeoutMS = this.socketOptions.connectTimeoutMS |
| 78 | ? this.socketOptions.connectTimeoutMS | |
| 79 | : 1000; | |
| 80 | ||
| 81 | // Socket connection timeout | |
| 82 | 0 | this.socketTimeoutMS = this.socketOptions.socketTimeoutMS |
| 83 | ? this.socketOptions.socketTimeoutMS | |
| 84 | : 30000; | |
| 85 | } | |
| 86 | ||
| 87 | 1 | Options.prototype.decorateAndClean = function(servers, callBackStore) { |
| 88 | 0 | var self = this; |
| 89 | ||
| 90 | // var de duplicate list | |
| 91 | 0 | var uniqueServers = {}; |
| 92 | // De-duplicate any servers in the seed list | |
| 93 | 0 | for(var i = 0; i < servers.length; i++) { |
| 94 | 0 | var server = servers[i]; |
| 95 | // If server does not exist set it | |
| 96 | 0 | if(uniqueServers[server.host + ":" + server.port] == null) { |
| 97 | 0 | uniqueServers[server.host + ":" + server.port] = server; |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | // Let's set the deduplicated list of servers | |
| 102 | 0 | var finalServers = []; |
| 103 | // Add the servers | |
| 104 | 0 | for(var key in uniqueServers) { |
| 105 | 0 | finalServers.push(uniqueServers[key]); |
| 106 | } | |
| 107 | ||
| 108 | 0 | finalServers.forEach(function(server) { |
| 109 | // Ensure no server has reconnect on | |
| 110 | 0 | server.options.auto_reconnect = false; |
| 111 | // Set up ssl options | |
| 112 | 0 | server.ssl = self.ssl; |
| 113 | 0 | server.sslValidate = self.sslValidate; |
| 114 | 0 | server.sslCA = self.sslCA; |
| 115 | 0 | server.sslCert = self.sslCert; |
| 116 | 0 | server.sslKey = self.sslKey; |
| 117 | 0 | server.sslPass = self.sslPass; |
| 118 | 0 | server.poolSize = self.poolSize; |
| 119 | // Set callback store | |
| 120 | 0 | server._callBackStore = callBackStore; |
| 121 | }); | |
| 122 | ||
| 123 | 0 | return finalServers; |
| 124 | } | |
| 125 | ||
| 126 | 1 | exports.Options = Options; |
| 127 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('../read_preference').ReadPreference |
| 2 | , DbCommand = require('../../commands/db_command').DbCommand | |
| 3 | , inherits = require('util').inherits | |
| 4 | , format = require('util').format | |
| 5 | , timers = require('timers') | |
| 6 | , Server = require('../server').Server | |
| 7 | , utils = require('../../utils') | |
| 8 | , PingStrategy = require('./strategies/ping_strategy').PingStrategy | |
| 9 | , StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy | |
| 10 | , Options = require('./options').Options | |
| 11 | , ReplSetState = require('./repl_set_state').ReplSetState | |
| 12 | , HighAvailabilityProcess = require('./ha').HighAvailabilityProcess | |
| 13 | , Base = require('../base').Base; | |
| 14 | ||
| 15 | 1 | var STATE_STARTING_PHASE_1 = 0; |
| 16 | 1 | var STATE_PRIMARY = 1; |
| 17 | 1 | var STATE_SECONDARY = 2; |
| 18 | 1 | var STATE_RECOVERING = 3; |
| 19 | 1 | var STATE_FATAL_ERROR = 4; |
| 20 | 1 | var STATE_STARTING_PHASE_2 = 5; |
| 21 | 1 | var STATE_UNKNOWN = 6; |
| 22 | 1 | var STATE_ARBITER = 7; |
| 23 | 1 | var STATE_DOWN = 8; |
| 24 | 1 | var STATE_ROLLBACK = 9; |
| 25 | ||
| 26 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 27 | 1 | var processor = require('../../utils').processor(); |
| 28 | ||
| 29 | /** | |
| 30 | * ReplSet constructor provides replicaset functionality | |
| 31 | * | |
| 32 | * Options | |
| 33 | * - **ha** {Boolean, default:true}, turn on high availability. | |
| 34 | * - **haInterval** {Number, default:2000}, time between each replicaset status check. | |
| 35 | * - **reconnectWait** {Number, default:1000}, time to wait in miliseconds before attempting reconnect. | |
| 36 | * - **retries** {Number, default:30}, number of times to attempt a replicaset reconnect. | |
| 37 | * - **rs_name** {String}, the name of the replicaset to connect to. | |
| 38 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 39 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 40 | * - **strategy** {String, default:'ping'}, selection strategy for reads choose between (ping, statistical and none, default is ping) | |
| 41 | * - **secondaryAcceptableLatencyMS** {Number, default:15}, sets the range of servers to pick when using NEAREST (lowest ping ms + the latency fence, ex: range of 1 to (1 + 15) ms) | |
| 42 | * - **connectWithNoPrimary** {Boolean, default:false}, sets if the driver should connect even if no primary is available | |
| 43 | * - **connectArbiter** {Boolean, default:false}, sets if the driver should connect to arbiters or not. | |
| 44 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 45 | * - **poolSize** {Number, default:5}, number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons. | |
| 46 | * - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support) | |
| 47 | * - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 48 | * - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 49 | * - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 50 | * - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 51 | * - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 52 | * | |
| 53 | * @class Represents a | |
| 54 | Replicaset Configuration | |
| 55 | * @param {Array} list of server objects participating in the replicaset. | |
| 56 | * @param {Object} [options] additional options for the replicaset connection. | |
| 57 | */ | |
| 58 | 1 | var ReplSet = exports.ReplSet = function(servers, options) { |
| 59 | // Set up basic | |
| 60 | 0 | if(!(this instanceof ReplSet)) |
| 61 | 0 | return new ReplSet(servers, options); |
| 62 | ||
| 63 | // Set up event emitter | |
| 64 | 0 | Base.call(this); |
| 65 | ||
| 66 | // Ensure we have a list of servers | |
| 67 | 0 | if(!Array.isArray(servers)) throw Error("The parameter must be an array of servers and contain at least one server"); |
| 68 | // Ensure no Mongos's | |
| 69 | 0 | for(var i = 0; i < servers.length; i++) { |
| 70 | 0 | if(!(servers[i] instanceof Server)) throw new Error("list of servers must be of type Server"); |
| 71 | } | |
| 72 | ||
| 73 | // Save the options | |
| 74 | 0 | this.options = new Options(options); |
| 75 | // Ensure basic validation of options | |
| 76 | 0 | this.options.init(); |
| 77 | ||
| 78 | // Server state | |
| 79 | 0 | this._serverState = ReplSet.REPLSET_DISCONNECTED; |
| 80 | // Add high availability process | |
| 81 | 0 | this._haProcess = new HighAvailabilityProcess(this, this.options); |
| 82 | ||
| 83 | // Let's iterate over all the provided server objects and decorate them | |
| 84 | 0 | this.servers = this.options.decorateAndClean(servers, this._callBackStore); |
| 85 | // Throw error if no seed servers | |
| 86 | 0 | if(this.servers.length == 0) throw new Error("No valid seed servers in the array"); |
| 87 | ||
| 88 | // Let's set up our strategy object for picking secondaries | |
| 89 | 0 | if(this.options.strategy == 'ping') { |
| 90 | // Create a new instance | |
| 91 | 0 | this.strategyInstance = new PingStrategy(this, this.options.secondaryAcceptableLatencyMS); |
| 92 | 0 | } else if(this.options.strategy == 'statistical') { |
| 93 | // Set strategy as statistical | |
| 94 | 0 | this.strategyInstance = new StatisticsStrategy(this); |
| 95 | // Add enable query information | |
| 96 | 0 | this.enableRecordQueryStats(true); |
| 97 | } | |
| 98 | ||
| 99 | 0 | this.emitOpen = this.options.emitOpen || true; |
| 100 | // Set up a clean state | |
| 101 | 0 | this._state = new ReplSetState(this); |
| 102 | // Current round robin selected server | |
| 103 | 0 | this._currentServerChoice = 0; |
| 104 | // Ensure up the server callbacks | |
| 105 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 106 | 0 | this.servers[i]._callBackStore = this._callBackStore; |
| 107 | 0 | this.servers[i].name = format("%s:%s", this.servers[i].host, this.servers[i].port) |
| 108 | 0 | this.servers[i].replicasetInstance = this; |
| 109 | 0 | this.servers[i].options.auto_reconnect = false; |
| 110 | 0 | this.servers[i].inheritReplSetOptionsFrom(this); |
| 111 | } | |
| 112 | ||
| 113 | // Allow setting the socketTimeoutMS on all connections | |
| 114 | // to work around issues such as secondaries blocking due to compaction | |
| 115 | 0 | utils.setSocketTimeoutProperty(this, this.options.socketOptions); |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * @ignore | |
| 120 | */ | |
| 121 | 1 | inherits(ReplSet, Base); |
| 122 | ||
| 123 | // Replicaset states | |
| 124 | 1 | ReplSet.REPLSET_CONNECTING = 'connecting'; |
| 125 | 1 | ReplSet.REPLSET_DISCONNECTED = 'disconnected'; |
| 126 | 1 | ReplSet.REPLSET_CONNECTED = 'connected'; |
| 127 | 1 | ReplSet.REPLSET_RECONNECTING = 'reconnecting'; |
| 128 | 1 | ReplSet.REPLSET_DESTROYED = 'destroyed'; |
| 129 | 1 | ReplSet.REPLSET_READ_ONLY = 'readonly'; |
| 130 | ||
| 131 | 1 | ReplSet.prototype.isAutoReconnect = function() { |
| 132 | 0 | return true; |
| 133 | } | |
| 134 | ||
| 135 | 1 | ReplSet.prototype.canWrite = function() { |
| 136 | 0 | return this._state.master && this._state.master.isConnected(); |
| 137 | } | |
| 138 | ||
| 139 | 1 | ReplSet.prototype.canRead = function(read) { |
| 140 | 0 | if((read == ReadPreference.PRIMARY |
| 141 | 0 | || read == null || read == false) && (this._state.master == null || !this._state.master.isConnected())) return false; |
| 142 | 0 | return Object.keys(this._state.secondaries).length > 0; |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * @ignore | |
| 147 | */ | |
| 148 | 1 | ReplSet.prototype.enableRecordQueryStats = function(enable) { |
| 149 | // Set the global enable record query stats | |
| 150 | 0 | this.recordQueryStats = enable; |
| 151 | ||
| 152 | // Enable all the servers | |
| 153 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 154 | 0 | this.servers[i].enableRecordQueryStats(enable); |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | /** | |
| 159 | * @ignore | |
| 160 | */ | |
| 161 | 1 | ReplSet.prototype.setReadPreference = function(preference) { |
| 162 | 0 | this.options.readPreference = preference; |
| 163 | } | |
| 164 | ||
| 165 | 1 | ReplSet.prototype.connect = function(parent, options, callback) { |
| 166 | 0 | if(this._serverState != ReplSet.REPLSET_DISCONNECTED) |
| 167 | 0 | return callback(new Error("in process of connection")); |
| 168 | ||
| 169 | // If no callback throw | |
| 170 | 0 | if(!(typeof callback == 'function')) |
| 171 | 0 | throw new Error("cannot call ReplSet.prototype.connect with no callback function"); |
| 172 | ||
| 173 | 0 | var self = this; |
| 174 | // Save db reference | |
| 175 | 0 | this.options.db = parent; |
| 176 | // Set replicaset as connecting | |
| 177 | 0 | this._serverState = ReplSet.REPLSET_CONNECTING |
| 178 | // Copy all the servers to our list of seeds | |
| 179 | 0 | var candidateServers = this.servers.slice(0); |
| 180 | // Pop the first server | |
| 181 | 0 | var server = candidateServers.pop(); |
| 182 | 0 | server.name = format("%s:%s", server.host, server.port); |
| 183 | // Set up the options | |
| 184 | 0 | var opts = { |
| 185 | returnIsMasterResults: true, | |
| 186 | eventReceiver: server | |
| 187 | } | |
| 188 | ||
| 189 | // Register some event listeners | |
| 190 | 0 | this.once("fullsetup", function(err, db, replset) { |
| 191 | // Set state to connected | |
| 192 | 0 | self._serverState = ReplSet.REPLSET_CONNECTED; |
| 193 | // Stop any process running | |
| 194 | 0 | if(self._haProcess) self._haProcess.stop(); |
| 195 | // Start the HA process | |
| 196 | 0 | self._haProcess.start(); |
| 197 | ||
| 198 | // Emit fullsetup | |
| 199 | 0 | processor(function() { |
| 200 | 0 | if(self.emitOpen) |
| 201 | 0 | self._emitAcrossAllDbInstances(self, null, "open", null, null, null); |
| 202 | ||
| 203 | 0 | self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null); |
| 204 | }); | |
| 205 | ||
| 206 | // If we have a strategy defined start it | |
| 207 | 0 | if(self.strategyInstance) { |
| 208 | 0 | self.strategyInstance.start(); |
| 209 | } | |
| 210 | ||
| 211 | // Finishing up the call | |
| 212 | 0 | callback(err, db, replset); |
| 213 | }); | |
| 214 | ||
| 215 | // Errors | |
| 216 | 0 | this.once("connectionError", function(err, result) { |
| 217 | 0 | callback(err, result); |
| 218 | }); | |
| 219 | ||
| 220 | // Attempt to connect to the server | |
| 221 | 0 | server.connect(this.options.db, opts, _connectHandler(this, candidateServers, server)); |
| 222 | } | |
| 223 | ||
| 224 | 1 | ReplSet.prototype.close = function(callback) { |
| 225 | 0 | var self = this; |
| 226 | // Set as destroyed | |
| 227 | 0 | this._serverState = ReplSet.REPLSET_DESTROYED; |
| 228 | // Stop the ha | |
| 229 | 0 | this._haProcess.stop(); |
| 230 | ||
| 231 | // If we have a strategy stop it | |
| 232 | 0 | if(this.strategyInstance) { |
| 233 | 0 | this.strategyInstance.stop(); |
| 234 | } | |
| 235 | ||
| 236 | // Kill all servers available | |
| 237 | 0 | for(var name in this._state.addresses) { |
| 238 | 0 | this._state.addresses[name].close(); |
| 239 | } | |
| 240 | ||
| 241 | // Clean out the state | |
| 242 | 0 | this._state = new ReplSetState(this); |
| 243 | ||
| 244 | // Emit close event | |
| 245 | 0 | processor(function() { |
| 246 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 247 | }); | |
| 248 | ||
| 249 | // Flush out any remaining call handlers | |
| 250 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 251 | ||
| 252 | // Callback | |
| 253 | 0 | if(typeof callback == 'function') |
| 254 | 0 | return callback(null, null); |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 258 | * Creates a new server for the `replset` based on `host`. | |
| 259 | * | |
| 260 | * @param {String} host - host:port pair (localhost:27017) | |
| 261 | * @param {ReplSet} replset - the ReplSet instance | |
| 262 | * @return {Server} | |
| 263 | * @ignore | |
| 264 | */ | |
| 265 | 1 | var createServer = function(self, host, options) { |
| 266 | // copy existing socket options to new server | |
| 267 | 0 | var socketOptions = {} |
| 268 | 0 | if(options.socketOptions) { |
| 269 | 0 | var keys = Object.keys(options.socketOptions); |
| 270 | 0 | for(var k = 0; k < keys.length; k++) { |
| 271 | 0 | socketOptions[keys[k]] = options.socketOptions[keys[k]]; |
| 272 | } | |
| 273 | } | |
| 274 | ||
| 275 | 0 | var parts = host.split(/:/); |
| 276 | 0 | if(1 === parts.length) { |
| 277 | 0 | parts[1] = Connection.DEFAULT_PORT; |
| 278 | } | |
| 279 | ||
| 280 | 0 | socketOptions.host = parts[0]; |
| 281 | 0 | socketOptions.port = parseInt(parts[1], 10); |
| 282 | ||
| 283 | 0 | var serverOptions = { |
| 284 | readPreference: options.readPreference, | |
| 285 | socketOptions: socketOptions, | |
| 286 | poolSize: options.poolSize, | |
| 287 | logger: options.logger, | |
| 288 | auto_reconnect: false, | |
| 289 | ssl: options.ssl, | |
| 290 | sslValidate: options.sslValidate, | |
| 291 | sslCA: options.sslCA, | |
| 292 | sslCert: options.sslCert, | |
| 293 | sslKey: options.sslKey, | |
| 294 | sslPass: options.sslPass | |
| 295 | } | |
| 296 | ||
| 297 | 0 | var server = new Server(socketOptions.host, socketOptions.port, serverOptions); |
| 298 | // Set up shared state | |
| 299 | 0 | server._callBackStore = self._callBackStore; |
| 300 | 0 | server.replicasetInstance = self; |
| 301 | 0 | server.enableRecordQueryStats(self.recordQueryStats); |
| 302 | // Set up event handlers | |
| 303 | 0 | server.on("close", _handler("close", self, server)); |
| 304 | 0 | server.on("error", _handler("error", self, server)); |
| 305 | 0 | server.on("timeout", _handler("timeout", self, server)); |
| 306 | 0 | return server; |
| 307 | } | |
| 308 | ||
| 309 | 1 | var _handler = function(event, self, server) { |
| 310 | 0 | return function(err, doc) { |
| 311 | // The event happened to a primary | |
| 312 | // Remove it from play | |
| 313 | 0 | if(self._state.isPrimary(server)) { |
| 314 | // Emit that the primary left the replicaset | |
| 315 | 0 | self.emit('left', 'primary', server); |
| 316 | // Get the current master | |
| 317 | 0 | var current_master = self._state.master; |
| 318 | 0 | self._state.master = null; |
| 319 | 0 | self._serverState = ReplSet.REPLSET_READ_ONLY; |
| 320 | ||
| 321 | 0 | if(current_master != null) { |
| 322 | // Unpack variables | |
| 323 | 0 | var host = current_master.socketOptions.host; |
| 324 | 0 | var port = current_master.socketOptions.port; |
| 325 | ||
| 326 | // Fire error on any unknown callbacks | |
| 327 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 328 | } | |
| 329 | 0 | } else if(self._state.isSecondary(server)) { |
| 330 | // Emit that a secondary left the replicaset | |
| 331 | 0 | self.emit('left', 'secondary', server); |
| 332 | // Delete from the list | |
| 333 | 0 | delete self._state.secondaries[server.name]; |
| 334 | } | |
| 335 | ||
| 336 | // If there is no more connections left and the setting is not destroyed | |
| 337 | // set to disconnected | |
| 338 | 0 | if(Object.keys(self._state.addresses).length == 0 |
| 339 | && self._serverState != ReplSet.REPLSET_DESTROYED) { | |
| 340 | 0 | self._serverState = ReplSet.REPLSET_DISCONNECTED; |
| 341 | ||
| 342 | // Emit close across all the attached db instances | |
| 343 | 0 | self._dbStore.emit("close", new Error("replicaset disconnected, no valid servers contactable over tcp"), null, true); |
| 344 | } | |
| 345 | ||
| 346 | // Unpack variables | |
| 347 | 0 | var host = server.socketOptions.host; |
| 348 | 0 | var port = server.socketOptions.port; |
| 349 | ||
| 350 | // Fire error on any unknown callbacks | |
| 351 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | 1 | var locateNewServers = function(self, state, candidateServers, ismaster) { |
| 356 | // Retrieve the host | |
| 357 | 0 | var hosts = ismaster.hosts; |
| 358 | // In candidate servers | |
| 359 | 0 | var inCandidateServers = function(name, candidateServers) { |
| 360 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 361 | 0 | if(candidateServers[i].name == name) return true; |
| 362 | } | |
| 363 | ||
| 364 | 0 | return false; |
| 365 | } | |
| 366 | ||
| 367 | // New servers | |
| 368 | 0 | var newServers = []; |
| 369 | 0 | if(Array.isArray(hosts)) { |
| 370 | // Let's go over all the hosts | |
| 371 | 0 | for(var i = 0; i < hosts.length; i++) { |
| 372 | 0 | if(!state.contains(hosts[i]) |
| 373 | && !inCandidateServers(hosts[i], candidateServers)) { | |
| 374 | 0 | newServers.push(createServer(self, hosts[i], self.options)); |
| 375 | } | |
| 376 | } | |
| 377 | } | |
| 378 | ||
| 379 | // Return list of possible new servers | |
| 380 | 0 | return newServers; |
| 381 | } | |
| 382 | ||
| 383 | 1 | var _connectHandler = function(self, candidateServers, instanceServer) { |
| 384 | 0 | return function(err, doc) { |
| 385 | // If we have an error add to the list | |
| 386 | 0 | if(err) { |
| 387 | 0 | self._state.errors[instanceServer.name] = instanceServer; |
| 388 | } else { | |
| 389 | 0 | delete self._state.errors[instanceServer.name]; |
| 390 | } | |
| 391 | ||
| 392 | 0 | if(!err) { |
| 393 | 0 | var ismaster = doc.documents[0] |
| 394 | ||
| 395 | // Error the server if | |
| 396 | 0 | if(!ismaster.ismaster |
| 397 | && !ismaster.secondary) { | |
| 398 | 0 | self._state.errors[instanceServer.name] = instanceServer; |
| 399 | } | |
| 400 | } | |
| 401 | ||
| 402 | ||
| 403 | // No error let's analyse the ismaster command | |
| 404 | 0 | if(!err && self._state.errors[instanceServer.name] == null) { |
| 405 | 0 | var ismaster = doc.documents[0] |
| 406 | ||
| 407 | // If no replicaset name exists set the current one | |
| 408 | 0 | if(self.options.rs_name == null) { |
| 409 | 0 | self.options.rs_name = ismaster.setName; |
| 410 | } | |
| 411 | ||
| 412 | // If we have a member that is not part of the set let's finish up | |
| 413 | 0 | if(typeof ismaster.setName == 'string' && ismaster.setName != self.options.rs_name) { |
| 414 | 0 | return self.emit("connectionError", new Error("Replicaset name " + ismaster.setName + " does not match specified name " + self.options.rs_name)); |
| 415 | } | |
| 416 | ||
| 417 | // Add the error handlers | |
| 418 | 0 | instanceServer.on("close", _handler("close", self, instanceServer)); |
| 419 | 0 | instanceServer.on("error", _handler("error", self, instanceServer)); |
| 420 | 0 | instanceServer.on("timeout", _handler("timeout", self, instanceServer)); |
| 421 | ||
| 422 | // Set any tags on the instance server | |
| 423 | 0 | instanceServer.name = ismaster.me; |
| 424 | 0 | instanceServer.tags = ismaster.tags; |
| 425 | ||
| 426 | // Add the server to the list | |
| 427 | 0 | self._state.addServer(instanceServer, ismaster); |
| 428 | ||
| 429 | // Check if we have more servers to add (only check when done with initial set) | |
| 430 | 0 | if(candidateServers.length == 0) { |
| 431 | // Get additional new servers that are not currently in set | |
| 432 | 0 | var new_servers = locateNewServers(self, self._state, candidateServers, ismaster); |
| 433 | ||
| 434 | // Locate any new servers that have not errored out yet | |
| 435 | 0 | for(var i = 0; i < new_servers.length; i++) { |
| 436 | 0 | if(self._state.errors[new_servers[i].name] == null) { |
| 437 | 0 | candidateServers.push(new_servers[i]) |
| 438 | } | |
| 439 | } | |
| 440 | } | |
| 441 | } | |
| 442 | ||
| 443 | // If the candidate server list is empty and no valid servers | |
| 444 | 0 | if(candidateServers.length == 0 && |
| 445 | !self._state.hasValidServers()) { | |
| 446 | 0 | return self.emit("connectionError", new Error("No valid replicaset instance servers found")); |
| 447 | 0 | } else if(candidateServers.length == 0) { |
| 448 | 0 | if(!self.options.connectWithNoPrimary && (self._state.master == null || !self._state.master.isConnected())) { |
| 449 | 0 | return self.emit("connectionError", new Error("No primary found in set")); |
| 450 | } | |
| 451 | 0 | return self.emit("fullsetup", null, self.options.db, self); |
| 452 | } | |
| 453 | ||
| 454 | // Let's connect the next server | |
| 455 | 0 | var nextServer = candidateServers.pop(); |
| 456 | ||
| 457 | // Set up the options | |
| 458 | 0 | var opts = { |
| 459 | returnIsMasterResults: true, | |
| 460 | eventReceiver: nextServer | |
| 461 | } | |
| 462 | ||
| 463 | // Attempt to connect to the server | |
| 464 | 0 | nextServer.connect(self.options.db, opts, _connectHandler(self, candidateServers, nextServer)); |
| 465 | } | |
| 466 | } | |
| 467 | ||
| 468 | 1 | ReplSet.prototype.isDestroyed = function() { |
| 469 | 0 | return this._serverState == ReplSet.REPLSET_DESTROYED; |
| 470 | } | |
| 471 | ||
| 472 | 1 | ReplSet.prototype.isConnected = function(read) { |
| 473 | 0 | var isConnected = false; |
| 474 | ||
| 475 | 0 | if(read == null || read == ReadPreference.PRIMARY || read == false) |
| 476 | 0 | isConnected = this._state.master != null && this._state.master.isConnected(); |
| 477 | ||
| 478 | 0 | if((read == ReadPreference.PRIMARY_PREFERRED || read == ReadPreference.SECONDARY_PREFERRED || read == ReadPreference.NEAREST) |
| 479 | && ((this._state.master != null && this._state.master.isConnected()) | |
| 480 | || (this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0))) { | |
| 481 | 0 | isConnected = true; |
| 482 | 0 | } else if(read == ReadPreference.SECONDARY) { |
| 483 | 0 | isConnected = this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0; |
| 484 | } | |
| 485 | ||
| 486 | // No valid connection return false | |
| 487 | 0 | return isConnected; |
| 488 | } | |
| 489 | ||
| 490 | 1 | ReplSet.prototype.isMongos = function() { |
| 491 | 0 | return false; |
| 492 | } | |
| 493 | ||
| 494 | 1 | ReplSet.prototype.checkoutWriter = function() { |
| 495 | 0 | if(this._state.master) return this._state.master.checkoutWriter(); |
| 496 | 0 | return new Error("no writer connection available"); |
| 497 | } | |
| 498 | ||
| 499 | 1 | ReplSet.prototype.processIsMaster = function(_server, _ismaster) { |
| 500 | // Server in recovery mode, remove it from available servers | |
| 501 | 0 | if(!_ismaster.ismaster && !_ismaster.secondary) { |
| 502 | // Locate the actual server | |
| 503 | 0 | var server = this._state.addresses[_server.name]; |
| 504 | // Close the server, simulating the closing of the connection | |
| 505 | // to get right removal semantics | |
| 506 | 0 | if(server) server.close(); |
| 507 | // Execute any callback errors | |
| 508 | 0 | _handler(null, this, server)(new Error("server is in recovery mode")); |
| 509 | } | |
| 510 | } | |
| 511 | ||
| 512 | 1 | ReplSet.prototype.allRawConnections = function() { |
| 513 | 0 | var connections = []; |
| 514 | ||
| 515 | 0 | for(var name in this._state.addresses) { |
| 516 | 0 | connections = connections.concat(this._state.addresses[name].allRawConnections()); |
| 517 | } | |
| 518 | ||
| 519 | 0 | return connections; |
| 520 | } | |
| 521 | ||
| 522 | /** | |
| 523 | * @ignore | |
| 524 | */ | |
| 525 | 1 | ReplSet.prototype.allServerInstances = function() { |
| 526 | 0 | var self = this; |
| 527 | // If no state yet return empty | |
| 528 | 0 | if(!self._state) return []; |
| 529 | // Close all the servers (concatenate entire list of servers first for ease) | |
| 530 | 0 | var allServers = self._state.master != null ? [self._state.master] : []; |
| 531 | ||
| 532 | // Secondary keys | |
| 533 | 0 | var keys = Object.keys(self._state.secondaries); |
| 534 | // Add all secondaries | |
| 535 | 0 | for(var i = 0; i < keys.length; i++) { |
| 536 | 0 | allServers.push(self._state.secondaries[keys[i]]); |
| 537 | } | |
| 538 | ||
| 539 | // Return complete list of all servers | |
| 540 | 0 | return allServers; |
| 541 | } | |
| 542 | ||
| 543 | /** | |
| 544 | * @ignore | |
| 545 | */ | |
| 546 | 1 | ReplSet.prototype.checkoutReader = function(readPreference, tags) { |
| 547 | 0 | var connection = null; |
| 548 | ||
| 549 | // If we have a read preference object unpack it | |
| 550 | 0 | if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') { |
| 551 | // Validate if the object is using a valid mode | |
| 552 | 0 | if(!readPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + readPreference.mode); |
| 553 | // Set the tag | |
| 554 | 0 | tags = readPreference.tags; |
| 555 | 0 | readPreference = readPreference.mode; |
| 556 | 0 | } else if(typeof readPreference == 'object' && readPreference['_type'] != 'ReadPreference') { |
| 557 | 0 | return new Error("read preferences must be either a string or an instance of ReadPreference"); |
| 558 | } | |
| 559 | ||
| 560 | // Set up our read Preference, allowing us to override the readPreference | |
| 561 | 0 | var finalReadPreference = readPreference != null ? readPreference : this.options.readPreference; |
| 562 | ||
| 563 | // Ensure we unpack a reference | |
| 564 | 0 | if(finalReadPreference != null && typeof finalReadPreference == 'object' && finalReadPreference['_type'] == 'ReadPreference') { |
| 565 | // Validate if the object is using a valid mode | |
| 566 | 0 | if(!finalReadPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + finalReadPreference.mode); |
| 567 | // Set the tag | |
| 568 | 0 | tags = finalReadPreference.tags; |
| 569 | 0 | readPreference = finalReadPreference.mode; |
| 570 | } | |
| 571 | ||
| 572 | // Finalize the read preference setup | |
| 573 | 0 | finalReadPreference = finalReadPreference == true ? ReadPreference.SECONDARY_PREFERRED : finalReadPreference; |
| 574 | 0 | finalReadPreference = finalReadPreference == null ? ReadPreference.PRIMARY : finalReadPreference; |
| 575 | ||
| 576 | // If we are reading from a primary | |
| 577 | 0 | if(finalReadPreference == 'primary') { |
| 578 | // If we provide a tags set send an error | |
| 579 | 0 | if(typeof tags == 'object' && tags != null) { |
| 580 | 0 | return new Error("PRIMARY cannot be combined with tags"); |
| 581 | } | |
| 582 | ||
| 583 | // If we provide a tags set send an error | |
| 584 | 0 | if(this._state.master == null) { |
| 585 | 0 | return new Error("No replica set primary available for query with ReadPreference PRIMARY"); |
| 586 | } | |
| 587 | ||
| 588 | // Checkout a writer | |
| 589 | 0 | return this.checkoutWriter(); |
| 590 | } | |
| 591 | ||
| 592 | // If we have specified to read from a secondary server grab a random one and read | |
| 593 | // from it, otherwise just pass the primary connection | |
| 594 | 0 | if((this.options.readSecondary || finalReadPreference == ReadPreference.SECONDARY_PREFERRED || finalReadPreference == ReadPreference.SECONDARY) && Object.keys(this._state.secondaries).length > 0) { |
| 595 | // If we have tags, look for servers matching the specific tag | |
| 596 | 0 | if(this.strategyInstance != null) { |
| 597 | // Only pick from secondaries | |
| 598 | 0 | var _secondaries = []; |
| 599 | 0 | for(var key in this._state.secondaries) { |
| 600 | 0 | _secondaries.push(this._state.secondaries[key]); |
| 601 | } | |
| 602 | ||
| 603 | 0 | if(finalReadPreference == ReadPreference.SECONDARY) { |
| 604 | // Check out the nearest from only the secondaries | |
| 605 | 0 | connection = this.strategyInstance.checkoutConnection(tags, _secondaries); |
| 606 | } else { | |
| 607 | 0 | connection = this.strategyInstance.checkoutConnection(tags, _secondaries); |
| 608 | // No candidate servers that match the tags, error | |
| 609 | 0 | if(connection == null || connection instanceof Error) { |
| 610 | // No secondary server avilable, attemp to checkout a primary server | |
| 611 | 0 | connection = this.checkoutWriter(); |
| 612 | // If no connection return an error | |
| 613 | 0 | if(connection == null || connection instanceof Error) { |
| 614 | 0 | return new Error("No replica set members available for query"); |
| 615 | } | |
| 616 | } | |
| 617 | } | |
| 618 | 0 | } else if(tags != null && typeof tags == 'object') { |
| 619 | // Get connection | |
| 620 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 621 | // No candidate servers that match the tags, error | |
| 622 | 0 | if(connection == null) { |
| 623 | 0 | return new Error("No replica set members available for query"); |
| 624 | } | |
| 625 | } else { | |
| 626 | 0 | connection = _roundRobin(this, tags); |
| 627 | } | |
| 628 | 0 | } else if(finalReadPreference == ReadPreference.PRIMARY_PREFERRED) { |
| 629 | // Check if there is a primary available and return that if possible | |
| 630 | 0 | connection = this.checkoutWriter(); |
| 631 | // If no connection available checkout a secondary | |
| 632 | 0 | if(connection == null || connection instanceof Error) { |
| 633 | // If we have tags, look for servers matching the specific tag | |
| 634 | 0 | if(tags != null && typeof tags == 'object') { |
| 635 | // Get connection | |
| 636 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 637 | // No candidate servers that match the tags, error | |
| 638 | 0 | if(connection == null) { |
| 639 | 0 | return new Error("No replica set members available for query"); |
| 640 | } | |
| 641 | } else { | |
| 642 | 0 | connection = _roundRobin(this, tags); |
| 643 | } | |
| 644 | } | |
| 645 | 0 | } else if(finalReadPreference == ReadPreference.SECONDARY_PREFERRED) { |
| 646 | // If we have tags, look for servers matching the specific tag | |
| 647 | 0 | if(this.strategyInstance != null) { |
| 648 | 0 | connection = this.strategyInstance.checkoutConnection(tags); |
| 649 | ||
| 650 | // No candidate servers that match the tags, error | |
| 651 | 0 | if(connection == null || connection instanceof Error) { |
| 652 | // No secondary server avilable, attemp to checkout a primary server | |
| 653 | 0 | connection = this.checkoutWriter(); |
| 654 | // If no connection return an error | |
| 655 | 0 | if(connection == null || connection instanceof Error) { |
| 656 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 657 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 658 | } | |
| 659 | } | |
| 660 | 0 | } else if(tags != null && typeof tags == 'object') { |
| 661 | // Get connection | |
| 662 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 663 | // No candidate servers that match the tags, error | |
| 664 | 0 | if(connection == null) { |
| 665 | // No secondary server avilable, attemp to checkout a primary server | |
| 666 | 0 | connection = this.checkoutWriter(); |
| 667 | // If no connection return an error | |
| 668 | 0 | if(connection == null || connection instanceof Error) { |
| 669 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 670 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 671 | } | |
| 672 | } | |
| 673 | } | |
| 674 | 0 | } else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance != null) { |
| 675 | 0 | connection = this.strategyInstance.checkoutConnection(tags); |
| 676 | 0 | } else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance == null) { |
| 677 | 0 | return new Error("A strategy for calculating nearness must be enabled such as ping or statistical"); |
| 678 | 0 | } else if(finalReadPreference == ReadPreference.SECONDARY && Object.keys(this._state.secondaries).length == 0) { |
| 679 | 0 | if(tags != null && typeof tags == 'object') { |
| 680 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 681 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 682 | } else { | |
| 683 | 0 | return new Error("No replica set secondary available for query with ReadPreference SECONDARY"); |
| 684 | } | |
| 685 | } else { | |
| 686 | 0 | connection = this.checkoutWriter(); |
| 687 | } | |
| 688 | ||
| 689 | // Return the connection | |
| 690 | 0 | return connection; |
| 691 | } | |
| 692 | ||
| 693 | /** | |
| 694 | * @ignore | |
| 695 | */ | |
| 696 | 1 | var _pickFromTags = function(self, tags) { |
| 697 | // If we have an array or single tag selection | |
| 698 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 699 | // Iterate over all tags until we find a candidate server | |
| 700 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 701 | // Grab a tag object | |
| 702 | 0 | var tagObject = tagObjects[_i]; |
| 703 | // Matching keys | |
| 704 | 0 | var matchingKeys = Object.keys(tagObject); |
| 705 | // Match all the servers that match the provdided tags | |
| 706 | 0 | var keys = Object.keys(self._state.secondaries); |
| 707 | 0 | var candidateServers = []; |
| 708 | ||
| 709 | 0 | for(var i = 0; i < keys.length; i++) { |
| 710 | 0 | var server = self._state.secondaries[keys[i]]; |
| 711 | // If we have tags match | |
| 712 | 0 | if(server.tags != null) { |
| 713 | 0 | var matching = true; |
| 714 | // Ensure we have all the values | |
| 715 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 716 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 717 | 0 | matching = false; |
| 718 | 0 | break; |
| 719 | } | |
| 720 | } | |
| 721 | ||
| 722 | // If we have a match add it to the list of matching servers | |
| 723 | 0 | if(matching) { |
| 724 | 0 | candidateServers.push(server); |
| 725 | } | |
| 726 | } | |
| 727 | } | |
| 728 | ||
| 729 | // If we have a candidate server return | |
| 730 | 0 | if(candidateServers.length > 0) { |
| 731 | 0 | if(self.strategyInstance) return self.strategyInstance.checkoutConnection(tags, candidateServers); |
| 732 | // Set instance to return | |
| 733 | 0 | return candidateServers[Math.floor(Math.random() * candidateServers.length)].checkoutReader(); |
| 734 | } | |
| 735 | } | |
| 736 | ||
| 737 | // No connection found | |
| 738 | 0 | return null; |
| 739 | } | |
| 740 | ||
| 741 | /** | |
| 742 | * Pick a secondary using round robin | |
| 743 | * | |
| 744 | * @ignore | |
| 745 | */ | |
| 746 | 1 | function _roundRobin (replset, tags) { |
| 747 | 0 | var keys = Object.keys(replset._state.secondaries); |
| 748 | // Update index | |
| 749 | 0 | replset._currentServerChoice = replset._currentServerChoice + 1; |
| 750 | // Pick a server | |
| 751 | 0 | var key = keys[replset._currentServerChoice % keys.length]; |
| 752 | ||
| 753 | 0 | var conn = null != replset._state.secondaries[key] |
| 754 | ? replset._state.secondaries[key].checkoutReader() | |
| 755 | : null; | |
| 756 | ||
| 757 | // If connection is null fallback to first available secondary | |
| 758 | 0 | if(null == conn) { |
| 759 | 0 | conn = pickFirstConnectedSecondary(replset, tags); |
| 760 | } | |
| 761 | ||
| 762 | 0 | return conn; |
| 763 | } | |
| 764 | ||
| 765 | /** | |
| 766 | * @ignore | |
| 767 | */ | |
| 768 | 1 | var pickFirstConnectedSecondary = function pickFirstConnectedSecondary(self, tags) { |
| 769 | 0 | var keys = Object.keys(self._state.secondaries); |
| 770 | 0 | var connection; |
| 771 | ||
| 772 | // Find first available reader if any | |
| 773 | 0 | for(var i = 0; i < keys.length; i++) { |
| 774 | 0 | connection = self._state.secondaries[keys[i]].checkoutReader(); |
| 775 | 0 | if(connection) return connection; |
| 776 | } | |
| 777 | ||
| 778 | // If we still have a null, read from primary if it's not secondary only | |
| 779 | 0 | if(self._readPreference == ReadPreference.SECONDARY_PREFERRED) { |
| 780 | 0 | connection = self._state.master.checkoutReader(); |
| 781 | 0 | if(connection) return connection; |
| 782 | } | |
| 783 | ||
| 784 | 0 | var preferenceName = self._readPreference == ReadPreference.SECONDARY_PREFERRED |
| 785 | ? 'secondary' | |
| 786 | : self._readPreference; | |
| 787 | ||
| 788 | 0 | return new Error("No replica set member available for query with ReadPreference " |
| 789 | + preferenceName + " and tags " + JSON.stringify(tags)); | |
| 790 | } | |
| 791 | ||
| 792 | /** | |
| 793 | * Get list of secondaries | |
| 794 | * @ignore | |
| 795 | */ | |
| 796 | 1 | Object.defineProperty(ReplSet.prototype, "secondaries", {enumerable: true |
| 797 | , get: function() { | |
| 798 | 0 | return utils.objectToArray(this._state.secondaries); |
| 799 | } | |
| 800 | }); | |
| 801 | ||
| 802 | /** | |
| 803 | * Get list of secondaries | |
| 804 | * @ignore | |
| 805 | */ | |
| 806 | 1 | Object.defineProperty(ReplSet.prototype, "arbiters", {enumerable: true |
| 807 | , get: function() { | |
| 808 | 0 | return utils.objectToArray(this._state.arbiters); |
| 809 | } | |
| 810 | }); | |
| 811 | ||
| 812 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Interval state object constructor | |
| 3 | * | |
| 4 | * @ignore | |
| 5 | */ | |
| 6 | 1 | var ReplSetState = function ReplSetState (replset) { |
| 7 | 0 | this.errorMessages = []; |
| 8 | 0 | this.secondaries = {}; |
| 9 | 0 | this.addresses = {}; |
| 10 | 0 | this.arbiters = {}; |
| 11 | 0 | this.passives = {}; |
| 12 | 0 | this.members = []; |
| 13 | 0 | this.errors = {}; |
| 14 | 0 | this.setName = null; |
| 15 | 0 | this.master = null; |
| 16 | 0 | this.replset = replset; |
| 17 | } | |
| 18 | ||
| 19 | 1 | ReplSetState.prototype.hasValidServers = function() { |
| 20 | 0 | var validServers = []; |
| 21 | 0 | if(this.master && this.master.isConnected()) return true; |
| 22 | ||
| 23 | 0 | if(this.secondaries) { |
| 24 | 0 | var keys = Object.keys(this.secondaries) |
| 25 | 0 | for(var i = 0; i < keys.length; i++) { |
| 26 | 0 | if(this.secondaries[keys[i]].isConnected()) |
| 27 | 0 | return true; |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 31 | 0 | return false; |
| 32 | } | |
| 33 | ||
| 34 | 1 | ReplSetState.prototype.getAllReadServers = function() { |
| 35 | 0 | var candidate_servers = []; |
| 36 | 0 | for(var name in this.addresses) { |
| 37 | 0 | candidate_servers.push(this.addresses[name]); |
| 38 | } | |
| 39 | ||
| 40 | // Return all possible read candidates | |
| 41 | 0 | return candidate_servers; |
| 42 | } | |
| 43 | ||
| 44 | 1 | ReplSetState.prototype.addServer = function(server, master) { |
| 45 | 0 | server.name = master.me; |
| 46 | ||
| 47 | 0 | if(master.ismaster) { |
| 48 | 0 | this.master = server; |
| 49 | 0 | this.addresses[server.name] = server; |
| 50 | 0 | this.replset.emit('joined', "primary", master, server); |
| 51 | 0 | } else if(master.secondary) { |
| 52 | 0 | this.secondaries[server.name] = server; |
| 53 | 0 | this.addresses[server.name] = server; |
| 54 | 0 | this.replset.emit('joined', "secondary", master, server); |
| 55 | 0 | } else if(master.arbiters) { |
| 56 | 0 | this.arbiters[server.name] = server; |
| 57 | 0 | this.addresses[server.name] = server; |
| 58 | 0 | this.replset.emit('joined', "arbiter", master, server); |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | 1 | ReplSetState.prototype.contains = function(host) { |
| 63 | 0 | return this.addresses[host] != null; |
| 64 | } | |
| 65 | ||
| 66 | 1 | ReplSetState.prototype.isPrimary = function(server) { |
| 67 | 0 | return this.master && this.master.name == server.name; |
| 68 | } | |
| 69 | ||
| 70 | 1 | ReplSetState.prototype.isSecondary = function(server) { |
| 71 | 0 | return this.secondaries[server.name] != null; |
| 72 | } | |
| 73 | ||
| 74 | 1 | exports.ReplSetState = ReplSetState; |
| 75 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Server = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/../../server").Server |
| 2 | , format = require('util').format; | |
| 3 | ||
| 4 | // The ping strategy uses pings each server and records the | |
| 5 | // elapsed time for the server so it can pick a server based on lowest | |
| 6 | // return time for the db command {ping:true} | |
| 7 | 1 | var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) { |
| 8 | 0 | this.replicaset = replicaset; |
| 9 | 0 | this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS; |
| 10 | 0 | this.state = 'disconnected'; |
| 11 | 0 | this.pingInterval = 5000; |
| 12 | // Class instance | |
| 13 | 0 | this.Db = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/../../../db").Db; |
| 14 | // Active db connections | |
| 15 | 0 | this.dbs = {}; |
| 16 | // Current server index | |
| 17 | 0 | this.index = 0; |
| 18 | // Logger api | |
| 19 | 0 | this.Logger = null; |
| 20 | } | |
| 21 | ||
| 22 | // Starts any needed code | |
| 23 | 1 | PingStrategy.prototype.start = function(callback) { |
| 24 | // already running? | |
| 25 | 0 | if ('connected' == this.state) return; |
| 26 | ||
| 27 | 0 | this.state = 'connected'; |
| 28 | ||
| 29 | // Start ping server | |
| 30 | 0 | this._pingServer(callback); |
| 31 | } | |
| 32 | ||
| 33 | // Stops and kills any processes running | |
| 34 | 1 | PingStrategy.prototype.stop = function(callback) { |
| 35 | // Stop the ping process | |
| 36 | 0 | this.state = 'disconnected'; |
| 37 | ||
| 38 | // Stop all the server instances | |
| 39 | 0 | for(var key in this.dbs) { |
| 40 | 0 | this.dbs[key].close(); |
| 41 | } | |
| 42 | ||
| 43 | // optional callback | |
| 44 | 0 | callback && callback(null, null); |
| 45 | } | |
| 46 | ||
| 47 | 1 | PingStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) { |
| 48 | // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS | |
| 49 | // Create a list of candidat servers, containing the primary if available | |
| 50 | 0 | var candidateServers = []; |
| 51 | 0 | var self = this; |
| 52 | ||
| 53 | // If we have not provided a list of candidate servers use the default setup | |
| 54 | 0 | if(!Array.isArray(secondaryCandidates)) { |
| 55 | 0 | candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : []; |
| 56 | // Add all the secondaries | |
| 57 | 0 | var keys = Object.keys(this.replicaset._state.secondaries); |
| 58 | 0 | for(var i = 0; i < keys.length; i++) { |
| 59 | 0 | candidateServers.push(this.replicaset._state.secondaries[keys[i]]) |
| 60 | } | |
| 61 | } else { | |
| 62 | 0 | candidateServers = secondaryCandidates; |
| 63 | } | |
| 64 | ||
| 65 | // Final list of eligable server | |
| 66 | 0 | var finalCandidates = []; |
| 67 | ||
| 68 | // If we have tags filter by tags | |
| 69 | 0 | if(tags != null && typeof tags == 'object') { |
| 70 | // If we have an array or single tag selection | |
| 71 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 72 | // Iterate over all tags until we find a candidate server | |
| 73 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 74 | // Grab a tag object | |
| 75 | 0 | var tagObject = tagObjects[_i]; |
| 76 | // Matching keys | |
| 77 | 0 | var matchingKeys = Object.keys(tagObject); |
| 78 | // Remove any that are not tagged correctly | |
| 79 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 80 | 0 | var server = candidateServers[i]; |
| 81 | // If we have tags match | |
| 82 | 0 | if(server.tags != null) { |
| 83 | 0 | var matching = true; |
| 84 | ||
| 85 | // Ensure we have all the values | |
| 86 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 87 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 88 | 0 | matching = false; |
| 89 | 0 | break; |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | // If we have a match add it to the list of matching servers | |
| 94 | 0 | if(matching) { |
| 95 | 0 | finalCandidates.push(server); |
| 96 | } | |
| 97 | } | |
| 98 | } | |
| 99 | } | |
| 100 | } else { | |
| 101 | // Final array candidates | |
| 102 | 0 | var finalCandidates = candidateServers; |
| 103 | } | |
| 104 | ||
| 105 | // Sort by ping time | |
| 106 | 0 | finalCandidates.sort(function(a, b) { |
| 107 | 0 | return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs']; |
| 108 | }); | |
| 109 | ||
| 110 | 0 | if(0 === finalCandidates.length) |
| 111 | 0 | return new Error("No replica set members available for query"); |
| 112 | ||
| 113 | // find lowest server with a ping time | |
| 114 | 0 | var lowest = finalCandidates.filter(function (server) { |
| 115 | 0 | return undefined != server.runtimeStats.pingMs; |
| 116 | })[0]; | |
| 117 | ||
| 118 | 0 | if(!lowest) { |
| 119 | 0 | lowest = finalCandidates[0]; |
| 120 | } | |
| 121 | ||
| 122 | // convert to integer | |
| 123 | 0 | var lowestPing = lowest.runtimeStats.pingMs | 0; |
| 124 | ||
| 125 | // determine acceptable latency | |
| 126 | 0 | var acceptable = lowestPing + this.secondaryAcceptableLatencyMS; |
| 127 | ||
| 128 | // remove any server responding slower than acceptable | |
| 129 | 0 | var len = finalCandidates.length; |
| 130 | 0 | while(len--) { |
| 131 | 0 | if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) { |
| 132 | 0 | finalCandidates.splice(len, 1); |
| 133 | } | |
| 134 | } | |
| 135 | ||
| 136 | 0 | if(self.logger && self.logger.debug) { |
| 137 | 0 | self.logger.debug("Ping strategy selection order for tags", tags); |
| 138 | 0 | finalCandidates.forEach(function(c) { |
| 139 | 0 | self.logger.debug(format("%s:%s = %s ms", c.host, c.port, c.runtimeStats['pingMs']), null); |
| 140 | }) | |
| 141 | } | |
| 142 | ||
| 143 | // If no candidates available return an error | |
| 144 | 0 | if(finalCandidates.length == 0) |
| 145 | 0 | return new Error("No replica set members available for query"); |
| 146 | ||
| 147 | // Ensure no we don't overflow | |
| 148 | 0 | this.index = this.index % finalCandidates.length |
| 149 | // Pick a random acceptable server | |
| 150 | 0 | var connection = finalCandidates[this.index].checkoutReader(); |
| 151 | // Point to next candidate (round robin style) | |
| 152 | 0 | this.index = this.index + 1; |
| 153 | ||
| 154 | 0 | if(self.logger && self.logger.debug) { |
| 155 | 0 | if(connection) |
| 156 | 0 | self.logger.debug("picked server %s:%s", connection.socketOptions.host, connection.socketOptions.port); |
| 157 | } | |
| 158 | ||
| 159 | 0 | return connection; |
| 160 | } | |
| 161 | ||
| 162 | 1 | PingStrategy.prototype._pingServer = function(callback) { |
| 163 | 0 | var self = this; |
| 164 | ||
| 165 | // Ping server function | |
| 166 | 0 | var pingFunction = function() { |
| 167 | // Our state changed to disconnected or destroyed return | |
| 168 | 0 | if(self.state == 'disconnected' || self.state == 'destroyed') return; |
| 169 | // If the replicaset is destroyed return | |
| 170 | 0 | if(self.replicaset.isDestroyed() || self.replicaset._serverState == 'disconnected') return |
| 171 | ||
| 172 | // Create a list of all servers we can send the ismaster command to | |
| 173 | 0 | var allServers = self.replicaset._state.master != null ? [self.replicaset._state.master] : []; |
| 174 | ||
| 175 | // Secondary keys | |
| 176 | 0 | var keys = Object.keys(self.replicaset._state.secondaries); |
| 177 | // Add all secondaries | |
| 178 | 0 | for(var i = 0; i < keys.length; i++) { |
| 179 | 0 | allServers.push(self.replicaset._state.secondaries[keys[i]]); |
| 180 | } | |
| 181 | ||
| 182 | // Number of server entries | |
| 183 | 0 | var numberOfEntries = allServers.length; |
| 184 | ||
| 185 | // We got keys | |
| 186 | 0 | for(var i = 0; i < allServers.length; i++) { |
| 187 | ||
| 188 | // We got a server instance | |
| 189 | 0 | var server = allServers[i]; |
| 190 | ||
| 191 | // Create a new server object, avoid using internal connections as they might | |
| 192 | // be in an illegal state | |
| 193 | 0 | new function(serverInstance) { |
| 194 | 0 | var _db = self.dbs[serverInstance.host + ":" + serverInstance.port]; |
| 195 | // If we have a db | |
| 196 | 0 | if(_db != null) { |
| 197 | // Startup time of the command | |
| 198 | 0 | var startTime = Date.now(); |
| 199 | ||
| 200 | // Execute ping command in own scope | |
| 201 | 0 | var _ping = function(__db, __serverInstance) { |
| 202 | // Execute ping on this connection | |
| 203 | 0 | __db.executeDbCommand({ping:1}, {failFast:true}, function(err) { |
| 204 | 0 | if(err) { |
| 205 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 206 | 0 | __db.close(); |
| 207 | 0 | return done(); |
| 208 | } | |
| 209 | ||
| 210 | 0 | if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) { |
| 211 | 0 | __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime; |
| 212 | } | |
| 213 | ||
| 214 | 0 | __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) { |
| 215 | 0 | if(err) { |
| 216 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 217 | 0 | __db.close(); |
| 218 | 0 | return done(); |
| 219 | } | |
| 220 | ||
| 221 | // Process the ismaster for the server | |
| 222 | 0 | if(result && result.documents && self.replicaset.processIsMaster) { |
| 223 | 0 | self.replicaset.processIsMaster(__serverInstance, result.documents[0]); |
| 224 | } | |
| 225 | ||
| 226 | // Done with the pinging | |
| 227 | 0 | done(); |
| 228 | }); | |
| 229 | }); | |
| 230 | }; | |
| 231 | // Ping | |
| 232 | 0 | _ping(_db, serverInstance); |
| 233 | } else { | |
| 234 | 0 | var connectTimeoutMS = self.replicaset.options.socketOptions |
| 235 | ? self.replicaset.options.socketOptions.connectTimeoutMS : 0 | |
| 236 | ||
| 237 | // Create a new master connection | |
| 238 | 0 | var _server = new Server(serverInstance.host, serverInstance.port, { |
| 239 | auto_reconnect: false, | |
| 240 | returnIsMasterResults: true, | |
| 241 | slaveOk: true, | |
| 242 | poolSize: 1, | |
| 243 | socketOptions: { connectTimeoutMS: connectTimeoutMS }, | |
| 244 | ssl: self.replicaset.options.ssl, | |
| 245 | sslValidate: self.replicaset.options.sslValidate, | |
| 246 | sslCA: self.replicaset.options.sslCA, | |
| 247 | sslCert: self.replicaset.options.sslCert, | |
| 248 | sslKey: self.replicaset.options.sslKey, | |
| 249 | sslPass: self.replicaset.options.sslPass | |
| 250 | }); | |
| 251 | ||
| 252 | // Create Db instance | |
| 253 | 0 | var _db = new self.Db('local', _server, { safe: true }); |
| 254 | 0 | _db.on("close", function() { |
| 255 | 0 | delete self.dbs[this.serverConfig.host + ":" + this.serverConfig.port]; |
| 256 | }) | |
| 257 | ||
| 258 | 0 | var _ping = function(__db, __serverInstance) { |
| 259 | 0 | if(self.state == 'disconnected') { |
| 260 | 0 | self.stop(); |
| 261 | 0 | return; |
| 262 | } | |
| 263 | ||
| 264 | 0 | __db.open(function(err, db) { |
| 265 | 0 | if(self.state == 'disconnected' && __db != null) { |
| 266 | 0 | return __db.close(); |
| 267 | } | |
| 268 | ||
| 269 | 0 | if(err) { |
| 270 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 271 | 0 | __db.close(); |
| 272 | 0 | return done(); |
| 273 | } | |
| 274 | ||
| 275 | // Save instance | |
| 276 | 0 | self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port] = __db; |
| 277 | ||
| 278 | // Startup time of the command | |
| 279 | 0 | var startTime = Date.now(); |
| 280 | ||
| 281 | // Execute ping on this connection | |
| 282 | 0 | __db.executeDbCommand({ping:1}, {failFast:true}, function(err) { |
| 283 | 0 | if(err) { |
| 284 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 285 | 0 | __db.close(); |
| 286 | 0 | return done(); |
| 287 | } | |
| 288 | ||
| 289 | 0 | if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) { |
| 290 | 0 | __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime; |
| 291 | } | |
| 292 | ||
| 293 | 0 | __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) { |
| 294 | 0 | if(err) { |
| 295 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 296 | 0 | __db.close(); |
| 297 | 0 | return done(); |
| 298 | } | |
| 299 | ||
| 300 | // Process the ismaster for the server | |
| 301 | 0 | if(result && result.documents && self.replicaset.processIsMaster) { |
| 302 | 0 | self.replicaset.processIsMaster(__serverInstance, result.documents[0]); |
| 303 | } | |
| 304 | ||
| 305 | // Done with the pinging | |
| 306 | 0 | done(); |
| 307 | }); | |
| 308 | }); | |
| 309 | }); | |
| 310 | }; | |
| 311 | ||
| 312 | // Ping the server | |
| 313 | 0 | _ping(_db, serverInstance); |
| 314 | } | |
| 315 | ||
| 316 | 0 | function done() { |
| 317 | // Adjust the number of checks | |
| 318 | 0 | numberOfEntries--; |
| 319 | ||
| 320 | // If we are done with all results coming back trigger ping again | |
| 321 | 0 | if(0 === numberOfEntries && 'connected' == self.state) { |
| 322 | 0 | setTimeout(pingFunction, self.pingInterval); |
| 323 | } | |
| 324 | } | |
| 325 | }(server); | |
| 326 | } | |
| 327 | } | |
| 328 | ||
| 329 | // Start pingFunction | |
| 330 | 0 | pingFunction(); |
| 331 | ||
| 332 | 0 | callback && callback(null); |
| 333 | } | |
| 334 |
| Line | Hits | Source |
|---|---|---|
| 1 | // The Statistics strategy uses the measure of each end-start time for each | |
| 2 | // query executed against the db to calculate the mean, variance and standard deviation | |
| 3 | // and pick the server which the lowest mean and deviation | |
| 4 | 1 | var StatisticsStrategy = exports.StatisticsStrategy = function(replicaset) { |
| 5 | 0 | this.replicaset = replicaset; |
| 6 | // Logger api | |
| 7 | 0 | this.Logger = null; |
| 8 | } | |
| 9 | ||
| 10 | // Starts any needed code | |
| 11 | 1 | StatisticsStrategy.prototype.start = function(callback) { |
| 12 | 0 | callback && callback(null, null); |
| 13 | } | |
| 14 | ||
| 15 | 1 | StatisticsStrategy.prototype.stop = function(callback) { |
| 16 | 0 | callback && callback(null, null); |
| 17 | } | |
| 18 | ||
| 19 | 1 | StatisticsStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) { |
| 20 | // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS | |
| 21 | // Create a list of candidat servers, containing the primary if available | |
| 22 | 0 | var candidateServers = []; |
| 23 | ||
| 24 | // If we have not provided a list of candidate servers use the default setup | |
| 25 | 0 | if(!Array.isArray(secondaryCandidates)) { |
| 26 | 0 | candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : []; |
| 27 | // Add all the secondaries | |
| 28 | 0 | var keys = Object.keys(this.replicaset._state.secondaries); |
| 29 | 0 | for(var i = 0; i < keys.length; i++) { |
| 30 | 0 | candidateServers.push(this.replicaset._state.secondaries[keys[i]]) |
| 31 | } | |
| 32 | } else { | |
| 33 | 0 | candidateServers = secondaryCandidates; |
| 34 | } | |
| 35 | ||
| 36 | // Final list of eligable server | |
| 37 | 0 | var finalCandidates = []; |
| 38 | ||
| 39 | // If we have tags filter by tags | |
| 40 | 0 | if(tags != null && typeof tags == 'object') { |
| 41 | // If we have an array or single tag selection | |
| 42 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 43 | // Iterate over all tags until we find a candidate server | |
| 44 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 45 | // Grab a tag object | |
| 46 | 0 | var tagObject = tagObjects[_i]; |
| 47 | // Matching keys | |
| 48 | 0 | var matchingKeys = Object.keys(tagObject); |
| 49 | // Remove any that are not tagged correctly | |
| 50 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 51 | 0 | var server = candidateServers[i]; |
| 52 | // If we have tags match | |
| 53 | 0 | if(server.tags != null) { |
| 54 | 0 | var matching = true; |
| 55 | ||
| 56 | // Ensure we have all the values | |
| 57 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 58 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 59 | 0 | matching = false; |
| 60 | 0 | break; |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | // If we have a match add it to the list of matching servers | |
| 65 | 0 | if(matching) { |
| 66 | 0 | finalCandidates.push(server); |
| 67 | } | |
| 68 | } | |
| 69 | } | |
| 70 | } | |
| 71 | } else { | |
| 72 | // Final array candidates | |
| 73 | 0 | var finalCandidates = candidateServers; |
| 74 | } | |
| 75 | ||
| 76 | // If no candidates available return an error | |
| 77 | 0 | if(finalCandidates.length == 0) return new Error("No replica set members available for query"); |
| 78 | // Pick a random server | |
| 79 | 0 | return finalCandidates[Math.round(Math.random(1000000) * (finalCandidates.length - 1))].checkoutReader(); |
| 80 | } | |
| 81 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Connection = require('./connection').Connection, |
| 2 | ReadPreference = require('./read_preference').ReadPreference, | |
| 3 | DbCommand = require('../commands/db_command').DbCommand, | |
| 4 | MongoReply = require('../responses/mongo_reply').MongoReply, | |
| 5 | ConnectionPool = require('./connection_pool').ConnectionPool, | |
| 6 | EventEmitter = require('events').EventEmitter, | |
| 7 | ServerCapabilities = require('./server_capabilities').ServerCapabilities, | |
| 8 | Base = require('./base').Base, | |
| 9 | format = require('util').format, | |
| 10 | utils = require('../utils'), | |
| 11 | timers = require('timers'), | |
| 12 | inherits = require('util').inherits; | |
| 13 | ||
| 14 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 15 | 1 | var processor = require('../utils').processor(); |
| 16 | ||
| 17 | /** | |
| 18 | * Class representing a single MongoDB Server connection | |
| 19 | * | |
| 20 | * Options | |
| 21 | * - **readPreference** {String, default:null}, set's the read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST) | |
| 22 | * - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support) | |
| 23 | * - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 24 | * - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 25 | * - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 26 | * - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 27 | * - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 28 | * - **poolSize** {Number, default:5}, number of connections in the connection pool, set to 5 as default for legacy reasons. | |
| 29 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 30 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 31 | * - **auto_reconnect** {Boolean, default:false}, reconnect on error. | |
| 32 | * - **disableDriverBSONSizeCheck** {Boolean, default:false}, force the server to error if the BSON message is to big | |
| 33 | * | |
| 34 | * @class Represents a Server connection. | |
| 35 | * @param {String} host the server host | |
| 36 | * @param {Number} port the server port | |
| 37 | * @param {Object} [options] optional options for insert command | |
| 38 | */ | |
| 39 | 1 | function Server(host, port, options) { |
| 40 | // Set up Server instance | |
| 41 | 0 | if(!(this instanceof Server)) return new Server(host, port, options); |
| 42 | ||
| 43 | // Set up event emitter | |
| 44 | 0 | Base.call(this); |
| 45 | ||
| 46 | // Ensure correct values | |
| 47 | 0 | if(port != null && typeof port == 'object') { |
| 48 | 0 | options = port; |
| 49 | 0 | port = Connection.DEFAULT_PORT; |
| 50 | } | |
| 51 | ||
| 52 | 0 | var self = this; |
| 53 | 0 | this.host = host; |
| 54 | 0 | this.port = port; |
| 55 | 0 | this.options = options == null ? {} : options; |
| 56 | 0 | this.internalConnection; |
| 57 | 0 | this.internalMaster = false; |
| 58 | 0 | this.connected = false; |
| 59 | 0 | this.poolSize = this.options.poolSize == null ? 5 : this.options.poolSize; |
| 60 | 0 | this.disableDriverBSONSizeCheck = this.options.disableDriverBSONSizeCheck != null ? this.options.disableDriverBSONSizeCheck : false; |
| 61 | 0 | this._used = false; |
| 62 | 0 | this.replicasetInstance = null; |
| 63 | ||
| 64 | // Emit open setup | |
| 65 | 0 | this.emitOpen = this.options.emitOpen || true; |
| 66 | // Set ssl as connection method | |
| 67 | 0 | this.ssl = this.options.ssl == null ? false : this.options.ssl; |
| 68 | // Set ssl validation | |
| 69 | 0 | this.sslValidate = this.options.sslValidate == null ? false : this.options.sslValidate; |
| 70 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 71 | 0 | this.sslCA = Array.isArray(this.options.sslCA) ? this.options.sslCA : null; |
| 72 | // Certificate to present to the server | |
| 73 | 0 | this.sslCert = this.options.sslCert; |
| 74 | // Certificate private key if in separate file | |
| 75 | 0 | this.sslKey = this.options.sslKey; |
| 76 | // Password to unlock private key | |
| 77 | 0 | this.sslPass = this.options.sslPass; |
| 78 | // Server capabilities | |
| 79 | 0 | this.serverCapabilities = null; |
| 80 | // Set server name | |
| 81 | 0 | this.name = format("%s:%s", host, port); |
| 82 | ||
| 83 | // Ensure we are not trying to validate with no list of certificates | |
| 84 | 0 | if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) { |
| 85 | 0 | throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate"); |
| 86 | } | |
| 87 | ||
| 88 | // Get the readPreference | |
| 89 | 0 | var readPreference = this.options['readPreference']; |
| 90 | // If readPreference is an object get the mode string | |
| 91 | 0 | var validateReadPreference = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; |
| 92 | // Read preference setting | |
| 93 | 0 | if(validateReadPreference != null) { |
| 94 | 0 | if(validateReadPreference != ReadPreference.PRIMARY && validateReadPreference != ReadPreference.SECONDARY && validateReadPreference != ReadPreference.NEAREST |
| 95 | && validateReadPreference != ReadPreference.SECONDARY_PREFERRED && validateReadPreference != ReadPreference.PRIMARY_PREFERRED) { | |
| 96 | 0 | throw new Error("Illegal readPreference mode specified, " + validateReadPreference); |
| 97 | } | |
| 98 | ||
| 99 | // Set read Preference | |
| 100 | 0 | this._readPreference = readPreference; |
| 101 | } else { | |
| 102 | 0 | this._readPreference = null; |
| 103 | } | |
| 104 | ||
| 105 | // Contains the isMaster information returned from the server | |
| 106 | 0 | this.isMasterDoc; |
| 107 | ||
| 108 | // Set default connection pool options | |
| 109 | 0 | this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; |
| 110 | 0 | if(this.disableDriverBSONSizeCheck) this.socketOptions.disableDriverBSONSizeCheck = this.disableDriverBSONSizeCheck; |
| 111 | ||
| 112 | // Set ssl up if it's defined | |
| 113 | 0 | if(this.ssl) { |
| 114 | 0 | this.socketOptions.ssl = true; |
| 115 | // Set ssl validation | |
| 116 | 0 | this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate; |
| 117 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 118 | 0 | this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null; |
| 119 | // Set certificate to present | |
| 120 | 0 | this.socketOptions.sslCert = this.sslCert; |
| 121 | // Set certificate to present | |
| 122 | 0 | this.socketOptions.sslKey = this.sslKey; |
| 123 | // Password to unlock private key | |
| 124 | 0 | this.socketOptions.sslPass = this.sslPass; |
| 125 | } | |
| 126 | ||
| 127 | // Set up logger if any set | |
| 128 | 0 | this.logger = this.options.logger != null |
| 129 | && (typeof this.options.logger.debug == 'function') | |
| 130 | && (typeof this.options.logger.error == 'function') | |
| 131 | && (typeof this.options.logger.log == 'function') | |
| 132 | ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 133 | ||
| 134 | // Just keeps list of events we allow | |
| 135 | 0 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[]}; |
| 136 | // Internal state of server connection | |
| 137 | 0 | this._serverState = 'disconnected'; |
| 138 | // Contains state information about server connection | |
| 139 | 0 | this._state = {'runtimeStats': {'queryStats':new RunningStats()}}; |
| 140 | // Do we record server stats or not | |
| 141 | 0 | this.recordQueryStats = false; |
| 142 | ||
| 143 | // Allow setting the socketTimeoutMS on all connections | |
| 144 | // to work around issues such as secondaries blocking due to compaction | |
| 145 | 0 | utils.setSocketTimeoutProperty(this, this.socketOptions); |
| 146 | }; | |
| 147 | ||
| 148 | /** | |
| 149 | * @ignore | |
| 150 | */ | |
| 151 | 1 | inherits(Server, Base); |
| 152 | ||
| 153 | // | |
| 154 | // Deprecated, USE ReadPreferences class | |
| 155 | // | |
| 156 | 1 | Server.READ_PRIMARY = ReadPreference.PRIMARY; |
| 157 | 1 | Server.READ_SECONDARY = ReadPreference.SECONDARY_PREFERRED; |
| 158 | 1 | Server.READ_SECONDARY_ONLY = ReadPreference.SECONDARY; |
| 159 | ||
| 160 | /** | |
| 161 | * Always ourselves | |
| 162 | * @ignore | |
| 163 | */ | |
| 164 | 1 | Server.prototype.setReadPreference = function(readPreference) { |
| 165 | 0 | this._readPreference = readPreference; |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * @ignore | |
| 170 | */ | |
| 171 | 1 | Server.prototype.isMongos = function() { |
| 172 | 0 | return this.isMasterDoc != null && this.isMasterDoc['msg'] == "isdbgrid" ? true : false; |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * @ignore | |
| 177 | */ | |
| 178 | 1 | Server.prototype._isUsed = function() { |
| 179 | 0 | return this._used; |
| 180 | } | |
| 181 | ||
| 182 | /** | |
| 183 | * @ignore | |
| 184 | */ | |
| 185 | 1 | Server.prototype.close = function(callback) { |
| 186 | // Set server status as disconnected | |
| 187 | 0 | this._serverState = 'destroyed'; |
| 188 | // Remove all local listeners | |
| 189 | 0 | this.removeAllListeners(); |
| 190 | ||
| 191 | 0 | if(this.connectionPool != null) { |
| 192 | // Remove all the listeners on the pool so it does not fire messages all over the place | |
| 193 | 0 | this.connectionPool.removeAllEventListeners(); |
| 194 | // Close the connection if it's open | |
| 195 | 0 | this.connectionPool.stop(true); |
| 196 | } | |
| 197 | ||
| 198 | // Emit close event | |
| 199 | 0 | if(this.db && !this.isSetMember()) { |
| 200 | 0 | var self = this; |
| 201 | 0 | processor(function() { |
| 202 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 203 | }) | |
| 204 | ||
| 205 | // Flush out any remaining call handlers | |
| 206 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 207 | } | |
| 208 | ||
| 209 | // Peform callback if present | |
| 210 | 0 | if(typeof callback === 'function') callback(null); |
| 211 | }; | |
| 212 | ||
| 213 | 1 | Server.prototype.isDestroyed = function() { |
| 214 | 0 | return this._serverState == 'destroyed'; |
| 215 | } | |
| 216 | ||
| 217 | /** | |
| 218 | * @ignore | |
| 219 | */ | |
| 220 | 1 | Server.prototype.isConnected = function() { |
| 221 | 0 | return this.connectionPool != null && this.connectionPool.isConnected(); |
| 222 | } | |
| 223 | ||
| 224 | /** | |
| 225 | * @ignore | |
| 226 | */ | |
| 227 | 1 | Server.prototype.canWrite = Server.prototype.isConnected; |
| 228 | 1 | Server.prototype.canRead = Server.prototype.isConnected; |
| 229 | ||
| 230 | 1 | Server.prototype.isAutoReconnect = function() { |
| 231 | 0 | if(this.isSetMember()) return false; |
| 232 | 0 | return this.options.auto_reconnect != null ? this.options.auto_reconnect : true; |
| 233 | } | |
| 234 | ||
| 235 | /** | |
| 236 | * @ignore | |
| 237 | */ | |
| 238 | 1 | Server.prototype.allServerInstances = function() { |
| 239 | 0 | return [this]; |
| 240 | } | |
| 241 | ||
| 242 | /** | |
| 243 | * @ignore | |
| 244 | */ | |
| 245 | 1 | Server.prototype.isSetMember = function() { |
| 246 | 0 | return this.replicasetInstance != null || this.mongosInstance != null; |
| 247 | } | |
| 248 | ||
| 249 | /** | |
| 250 | * Assigns a replica set to this `server`. | |
| 251 | * | |
| 252 | * @param {ReplSet} replset | |
| 253 | * @ignore | |
| 254 | */ | |
| 255 | 1 | Server.prototype.assignReplicaSet = function (replset) { |
| 256 | 0 | this.replicasetInstance = replset; |
| 257 | 0 | this.inheritReplSetOptionsFrom(replset); |
| 258 | 0 | this.enableRecordQueryStats(replset.recordQueryStats); |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Takes needed options from `replset` and overwrites | |
| 263 | * our own options. | |
| 264 | * | |
| 265 | * @param {ReplSet} replset | |
| 266 | * @ignore | |
| 267 | */ | |
| 268 | 1 | Server.prototype.inheritReplSetOptionsFrom = function (replset) { |
| 269 | 0 | this.socketOptions = {}; |
| 270 | 0 | this.socketOptions.connectTimeoutMS = replset.options.socketOptions.connectTimeoutMS || 30000; |
| 271 | ||
| 272 | 0 | if(replset.options.ssl) { |
| 273 | // Set ssl on | |
| 274 | 0 | this.socketOptions.ssl = true; |
| 275 | // Set ssl validation | |
| 276 | 0 | this.socketOptions.sslValidate = replset.options.sslValidate == null ? false : replset.options.sslValidate; |
| 277 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 278 | 0 | this.socketOptions.sslCA = Array.isArray(replset.options.sslCA) ? replset.options.sslCA : null; |
| 279 | // Set certificate to present | |
| 280 | 0 | this.socketOptions.sslCert = replset.options.sslCert; |
| 281 | // Set certificate to present | |
| 282 | 0 | this.socketOptions.sslKey = replset.options.sslKey; |
| 283 | // Password to unlock private key | |
| 284 | 0 | this.socketOptions.sslPass = replset.options.sslPass; |
| 285 | } | |
| 286 | ||
| 287 | // If a socket option object exists clone it | |
| 288 | 0 | if(utils.isObject(replset.options.socketOptions)) { |
| 289 | 0 | var keys = Object.keys(replset.options.socketOptions); |
| 290 | 0 | for(var i = 0; i < keys.length; i++) |
| 291 | 0 | this.socketOptions[keys[i]] = replset.options.socketOptions[keys[i]]; |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | /** | |
| 296 | * Opens this server connection. | |
| 297 | * | |
| 298 | * @ignore | |
| 299 | */ | |
| 300 | 1 | Server.prototype.connect = function(dbInstance, options, callback) { |
| 301 | 0 | if('function' === typeof options) callback = options, options = {}; |
| 302 | 0 | if(options == null) options = {}; |
| 303 | 0 | if(!('function' === typeof callback)) callback = null; |
| 304 | 0 | var self = this; |
| 305 | // Save the options | |
| 306 | 0 | this.options = options; |
| 307 | ||
| 308 | // Currently needed to work around problems with multiple connections in a pool with ssl | |
| 309 | // TODO fix if possible | |
| 310 | 0 | if(this.ssl == true) { |
| 311 | // Set up socket options for ssl | |
| 312 | 0 | this.socketOptions.ssl = true; |
| 313 | // Set ssl validation | |
| 314 | 0 | this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate; |
| 315 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 316 | 0 | this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null; |
| 317 | // Set certificate to present | |
| 318 | 0 | this.socketOptions.sslCert = this.sslCert; |
| 319 | // Set certificate to present | |
| 320 | 0 | this.socketOptions.sslKey = this.sslKey; |
| 321 | // Password to unlock private key | |
| 322 | 0 | this.socketOptions.sslPass = this.sslPass; |
| 323 | } | |
| 324 | ||
| 325 | // Let's connect | |
| 326 | 0 | var server = this; |
| 327 | // Let's us override the main receiver of events | |
| 328 | 0 | var eventReceiver = options.eventReceiver != null ? options.eventReceiver : this; |
| 329 | // Save reference to dbInstance | |
| 330 | 0 | this.db = dbInstance; // `db` property matches ReplSet and Mongos |
| 331 | 0 | this.dbInstances = [dbInstance]; |
| 332 | ||
| 333 | // Force connection pool if there is one | |
| 334 | 0 | if(server.connectionPool) server.connectionPool.stop(); |
| 335 | // Set server state to connecting | |
| 336 | 0 | this._serverState = 'connecting'; |
| 337 | ||
| 338 | 0 | if(server.connectionPool != null) { |
| 339 | // Remove all the listeners on the pool so it does not fire messages all over the place | |
| 340 | 0 | this.connectionPool.removeAllEventListeners(); |
| 341 | // Close the connection if it's open | |
| 342 | 0 | this.connectionPool.stop(true); |
| 343 | } | |
| 344 | ||
| 345 | 0 | this.connectionPool = new ConnectionPool(this.host, this.port, this.poolSize, dbInstance.bson, this.socketOptions); |
| 346 | 0 | var connectionPool = this.connectionPool; |
| 347 | // If ssl is not enabled don't wait between the pool connections | |
| 348 | 0 | if(this.ssl == null || !this.ssl) connectionPool._timeToWait = null; |
| 349 | // Set logger on pool | |
| 350 | 0 | connectionPool.logger = this.logger; |
| 351 | 0 | connectionPool.bson = dbInstance.bson; |
| 352 | ||
| 353 | // Set basic parameters passed in | |
| 354 | 0 | var returnIsMasterResults = options.returnIsMasterResults == null ? false : options.returnIsMasterResults; |
| 355 | ||
| 356 | // Create a default connect handler, overriden when using replicasets | |
| 357 | 0 | var connectCallback = function(_server) { |
| 358 | 0 | return function(err, reply) { |
| 359 | // ensure no callbacks get called twice | |
| 360 | 0 | var internalCallback = callback; |
| 361 | 0 | callback = null; |
| 362 | ||
| 363 | // Assign the server | |
| 364 | 0 | _server = _server != null ? _server : server; |
| 365 | ||
| 366 | // If something close down the connection and removed the callback before | |
| 367 | // proxy killed connection etc, ignore the erorr as close event was isssued | |
| 368 | 0 | if(err != null && internalCallback == null) return; |
| 369 | // Internal callback | |
| 370 | 0 | if(err != null) return internalCallback(err, null, _server); |
| 371 | 0 | _server.master = reply.documents[0].ismaster == 1 ? true : false; |
| 372 | 0 | _server.connectionPool.setMaxBsonSize(reply.documents[0].maxBsonObjectSize); |
| 373 | 0 | _server.connectionPool.setMaxMessageSizeBytes(reply.documents[0].maxMessageSizeBytes); |
| 374 | // Set server state to connEcted | |
| 375 | 0 | _server._serverState = 'connected'; |
| 376 | // Set server as connected | |
| 377 | 0 | _server.connected = true; |
| 378 | // Save document returned so we can query it | |
| 379 | 0 | _server.isMasterDoc = reply.documents[0]; |
| 380 | ||
| 381 | 0 | if(self.emitOpen) { |
| 382 | 0 | _server._emitAcrossAllDbInstances(_server, eventReceiver, "open", null, returnIsMasterResults ? reply : null, null); |
| 383 | 0 | self.emitOpen = false; |
| 384 | } else { | |
| 385 | 0 | _server._emitAcrossAllDbInstances(_server, eventReceiver, "reconnect", null, returnIsMasterResults ? reply : null, null); |
| 386 | } | |
| 387 | ||
| 388 | // Set server capabilities | |
| 389 | 0 | server.serverCapabilities = new ServerCapabilities(_server.isMasterDoc); |
| 390 | ||
| 391 | // If we have it set to returnIsMasterResults | |
| 392 | 0 | if(returnIsMasterResults) { |
| 393 | 0 | internalCallback(null, reply, _server); |
| 394 | } else { | |
| 395 | 0 | internalCallback(null, dbInstance, _server); |
| 396 | } | |
| 397 | } | |
| 398 | }; | |
| 399 | ||
| 400 | // Let's us override the main connect callback | |
| 401 | 0 | var connectHandler = options.connectHandler == null ? connectCallback(server) : options.connectHandler; |
| 402 | ||
| 403 | // Set up on connect method | |
| 404 | 0 | connectionPool.on("poolReady", function() { |
| 405 | // Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | |
| 406 | 0 | var db_command = DbCommand.NcreateIsMasterCommand(dbInstance, dbInstance.databaseName); |
| 407 | // Check out a reader from the pool | |
| 408 | 0 | var connection = connectionPool.checkoutConnection(); |
| 409 | // Register handler for messages | |
| 410 | 0 | server._registerHandler(db_command, false, connection, connectHandler); |
| 411 | // Write the command out | |
| 412 | 0 | connection.write(db_command); |
| 413 | }) | |
| 414 | ||
| 415 | // Set up item connection | |
| 416 | 0 | connectionPool.on("message", function(message) { |
| 417 | // Attempt to parse the message | |
| 418 | 0 | try { |
| 419 | // Create a new mongo reply | |
| 420 | 0 | var mongoReply = new MongoReply() |
| 421 | // Parse the header | |
| 422 | 0 | mongoReply.parseHeader(message, connectionPool.bson) |
| 423 | ||
| 424 | // If message size is not the same as the buffer size | |
| 425 | // something went terribly wrong somewhere | |
| 426 | 0 | if(mongoReply.messageLength != message.length) { |
| 427 | // Emit the error | |
| 428 | 0 | if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", new Error("bson length is different from message length"), server); |
| 429 | // Remove all listeners | |
| 430 | 0 | server.removeAllListeners(); |
| 431 | } else { | |
| 432 | 0 | var startDate = new Date().getTime(); |
| 433 | ||
| 434 | // Callback instance | |
| 435 | 0 | var callbackInfo = server._findHandler(mongoReply.responseTo.toString()); |
| 436 | ||
| 437 | // The command executed another request, log the handler again under that request id | |
| 438 | 0 | if(mongoReply.requestId > 0 && mongoReply.cursorId.toString() != "0" |
| 439 | && callbackInfo && callbackInfo.info && callbackInfo.info.exhaust) { | |
| 440 | 0 | server._reRegisterHandler(mongoReply.requestId, callbackInfo); |
| 441 | } | |
| 442 | // Parse the body | |
| 443 | 0 | mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { |
| 444 | 0 | if(err != null) { |
| 445 | // If pool connection is already closed | |
| 446 | 0 | if(server._serverState === 'disconnected') return; |
| 447 | // Set server state to disconnected | |
| 448 | 0 | server._serverState = 'disconnected'; |
| 449 | // Remove all listeners and close the connection pool | |
| 450 | 0 | server.removeAllListeners(); |
| 451 | 0 | connectionPool.stop(true); |
| 452 | ||
| 453 | // If we have a callback return the error | |
| 454 | 0 | if(typeof callback === 'function') { |
| 455 | // ensure no callbacks get called twice | |
| 456 | 0 | var internalCallback = callback; |
| 457 | 0 | callback = null; |
| 458 | // Perform callback | |
| 459 | 0 | internalCallback(err, null, server); |
| 460 | 0 | } else if(server.isSetMember()) { |
| 461 | 0 | if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", utils.toError(err), server); |
| 462 | } else { | |
| 463 | 0 | if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", utils.toError(err), server); |
| 464 | } | |
| 465 | ||
| 466 | // If we are a single server connection fire errors correctly | |
| 467 | 0 | if(!server.isSetMember()) { |
| 468 | // Fire all callback errors | |
| 469 | 0 | server.__executeAllCallbacksWithError(err); |
| 470 | // Emit error | |
| 471 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); |
| 472 | } | |
| 473 | // Short cut | |
| 474 | 0 | return; |
| 475 | } | |
| 476 | ||
| 477 | // Let's record the stats info if it's enabled | |
| 478 | 0 | if(server.recordQueryStats == true && server._state['runtimeStats'] != null |
| 479 | && server._state.runtimeStats['queryStats'] instanceof RunningStats) { | |
| 480 | // Add data point to the running statistics object | |
| 481 | 0 | server._state.runtimeStats.queryStats.push(new Date().getTime() - callbackInfo.info.start); |
| 482 | } | |
| 483 | ||
| 484 | // Dispatch the call | |
| 485 | 0 | server._callHandler(mongoReply.responseTo, mongoReply, null); |
| 486 | ||
| 487 | // If we have an error about the server not being master or primary | |
| 488 | 0 | if((mongoReply.responseFlag & (1 << 1)) != 0 |
| 489 | && mongoReply.documents[0].code | |
| 490 | && mongoReply.documents[0].code == 13436) { | |
| 491 | 0 | server.close(); |
| 492 | } | |
| 493 | }); | |
| 494 | } | |
| 495 | } catch (err) { | |
| 496 | // Throw error in next tick | |
| 497 | 0 | processor(function() { |
| 498 | 0 | throw err; |
| 499 | }) | |
| 500 | } | |
| 501 | }); | |
| 502 | ||
| 503 | // Handle timeout | |
| 504 | 0 | connectionPool.on("timeout", function(err) { |
| 505 | // If pool connection is already closed | |
| 506 | 0 | if(server._serverState === 'disconnected' |
| 507 | 0 | || server._serverState === 'destroyed') return; |
| 508 | // Set server state to disconnected | |
| 509 | 0 | server._serverState = 'disconnected'; |
| 510 | // If we have a callback return the error | |
| 511 | 0 | if(typeof callback === 'function') { |
| 512 | // ensure no callbacks get called twice | |
| 513 | 0 | var internalCallback = callback; |
| 514 | 0 | callback = null; |
| 515 | // Perform callback | |
| 516 | 0 | internalCallback(err, null, server); |
| 517 | 0 | } else if(server.isSetMember()) { |
| 518 | 0 | if(server.listeners("timeout") && server.listeners("timeout").length > 0) server.emit("timeout", err, server); |
| 519 | } else { | |
| 520 | 0 | if(eventReceiver.listeners("timeout") && eventReceiver.listeners("timeout").length > 0) eventReceiver.emit("timeout", err, server); |
| 521 | } | |
| 522 | ||
| 523 | // If we are a single server connection fire errors correctly | |
| 524 | 0 | if(!server.isSetMember()) { |
| 525 | // Fire all callback errors | |
| 526 | 0 | server.__executeAllCallbacksWithError(err); |
| 527 | // Emit error | |
| 528 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "timeout", err, server, true); |
| 529 | } | |
| 530 | ||
| 531 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 532 | 0 | if(server.isAutoReconnect() |
| 533 | && !server.isSetMember() | |
| 534 | && (server._serverState != 'destroyed') | |
| 535 | && !server._reconnectInProgreess) { | |
| 536 | // Set the number of retries | |
| 537 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 538 | // Attempt reconnect | |
| 539 | 0 | server._reconnectInProgreess = true; |
| 540 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 541 | } | |
| 542 | }); | |
| 543 | ||
| 544 | // Handle errors | |
| 545 | 0 | connectionPool.on("error", function(message, connection, error_options) { |
| 546 | // If pool connection is already closed | |
| 547 | 0 | if(server._serverState === 'disconnected' |
| 548 | 0 | || server._serverState === 'destroyed') return; |
| 549 | ||
| 550 | // Set server state to disconnected | |
| 551 | 0 | server._serverState = 'disconnected'; |
| 552 | // Error message | |
| 553 | 0 | var error_message = new Error(message && message.err ? message.err : message); |
| 554 | // Error message coming from ssl | |
| 555 | 0 | if(error_options && error_options.ssl) error_message.ssl = true; |
| 556 | ||
| 557 | // If we have a callback return the error | |
| 558 | 0 | if(typeof callback === 'function') { |
| 559 | // ensure no callbacks get called twice | |
| 560 | 0 | var internalCallback = callback; |
| 561 | 0 | callback = null; |
| 562 | // Perform callback | |
| 563 | 0 | internalCallback(error_message, null, server); |
| 564 | 0 | } else if(server.isSetMember()) { |
| 565 | 0 | if(server.listeners("error") && server.listeners("error").length > 0) server.emit("error", error_message, server); |
| 566 | } else { | |
| 567 | 0 | if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", error_message, server); |
| 568 | } | |
| 569 | ||
| 570 | // If we are a single server connection fire errors correctly | |
| 571 | 0 | if(!server.isSetMember()) { |
| 572 | // Fire all callback errors | |
| 573 | 0 | server.__executeAllCallbacksWithError(error_message); |
| 574 | // Emit error | |
| 575 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "error", error_message, server, true); |
| 576 | } | |
| 577 | ||
| 578 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 579 | 0 | if(server.isAutoReconnect() |
| 580 | && !server.isSetMember() | |
| 581 | && (server._serverState != 'destroyed') | |
| 582 | && !server._reconnectInProgreess) { | |
| 583 | ||
| 584 | // Set the number of retries | |
| 585 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 586 | // Attempt reconnect | |
| 587 | 0 | server._reconnectInProgreess = true; |
| 588 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 589 | } | |
| 590 | }); | |
| 591 | ||
| 592 | // Handle close events | |
| 593 | 0 | connectionPool.on("close", function() { |
| 594 | // If pool connection is already closed | |
| 595 | 0 | if(server._serverState === 'disconnected' |
| 596 | 0 | || server._serverState === 'destroyed') return; |
| 597 | // Set server state to disconnected | |
| 598 | 0 | server._serverState = 'disconnected'; |
| 599 | // If we have a callback return the error | |
| 600 | 0 | if(typeof callback == 'function') { |
| 601 | // ensure no callbacks get called twice | |
| 602 | 0 | var internalCallback = callback; |
| 603 | 0 | callback = null; |
| 604 | // Perform callback | |
| 605 | 0 | internalCallback(new Error("connection closed"), null, server); |
| 606 | 0 | } else if(server.isSetMember()) { |
| 607 | 0 | if(server.listeners("close") && server.listeners("close").length > 0) server.emit("close", new Error("connection closed"), server); |
| 608 | } else { | |
| 609 | 0 | if(eventReceiver.listeners("close") && eventReceiver.listeners("close").length > 0) eventReceiver.emit("close", new Error("connection closed"), server); |
| 610 | } | |
| 611 | ||
| 612 | // If we are a single server connection fire errors correctly | |
| 613 | 0 | if(!server.isSetMember()) { |
| 614 | // Fire all callback errors | |
| 615 | 0 | server.__executeAllCallbacksWithError(new Error("connection closed")); |
| 616 | // Emit error | |
| 617 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "close", server, null, true); |
| 618 | } | |
| 619 | ||
| 620 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 621 | 0 | if(server.isAutoReconnect() |
| 622 | && !server.isSetMember() | |
| 623 | && (server._serverState != 'destroyed') | |
| 624 | && !server._reconnectInProgreess) { | |
| 625 | ||
| 626 | // Set the number of retries | |
| 627 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 628 | // Attempt reconnect | |
| 629 | 0 | server._reconnectInProgreess = true; |
| 630 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 631 | } | |
| 632 | }); | |
| 633 | ||
| 634 | /** | |
| 635 | * @ignore | |
| 636 | */ | |
| 637 | 0 | var __attemptReconnect = function(server) { |
| 638 | 0 | return function() { |
| 639 | // Attempt reconnect | |
| 640 | 0 | server.connect(server.db, server.options, function(err, result) { |
| 641 | 0 | server._reconnect_retries = server._reconnect_retries - 1; |
| 642 | ||
| 643 | 0 | if(err) { |
| 644 | // Retry | |
| 645 | 0 | if(server._reconnect_retries == 0 || server._serverState == 'destroyed') { |
| 646 | 0 | server._serverState = 'connected'; |
| 647 | 0 | server._reconnectInProgreess = false |
| 648 | // Fire all callback errors | |
| 649 | 0 | return server.__executeAllCallbacksWithError(new Error("failed to reconnect to server")); |
| 650 | } else { | |
| 651 | 0 | return setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 652 | } | |
| 653 | } else { | |
| 654 | // Set as authenticating (isConnected will be false) | |
| 655 | 0 | server._serverState = 'authenticating'; |
| 656 | // Apply any auths, we don't try to catch any errors here | |
| 657 | // as there are nowhere to simply propagate them to | |
| 658 | 0 | self._apply_auths(server.db, function(err, result) { |
| 659 | 0 | server._serverState = 'connected'; |
| 660 | 0 | server._reconnectInProgreess = false; |
| 661 | 0 | server._commandsStore.execute_queries(); |
| 662 | 0 | server._commandsStore.execute_writes(); |
| 663 | }); | |
| 664 | } | |
| 665 | }); | |
| 666 | } | |
| 667 | } | |
| 668 | ||
| 669 | // If we have a parser error we are in an unknown state, close everything and emit | |
| 670 | // error | |
| 671 | 0 | connectionPool.on("parseError", function(err) { |
| 672 | // If pool connection is already closed | |
| 673 | 0 | if(server._serverState === 'disconnected' |
| 674 | 0 | || server._serverState === 'destroyed') return; |
| 675 | // Set server state to disconnected | |
| 676 | 0 | server._serverState = 'disconnected'; |
| 677 | // If we have a callback return the error | |
| 678 | 0 | if(typeof callback === 'function') { |
| 679 | // ensure no callbacks get called twice | |
| 680 | 0 | var internalCallback = callback; |
| 681 | 0 | callback = null; |
| 682 | // Perform callback | |
| 683 | 0 | internalCallback(utils.toError(err), null, server); |
| 684 | 0 | } else if(server.isSetMember()) { |
| 685 | 0 | if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", utils.toError(err), server); |
| 686 | } else { | |
| 687 | 0 | if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", utils.toError(err), server); |
| 688 | } | |
| 689 | ||
| 690 | // If we are a single server connection fire errors correctly | |
| 691 | 0 | if(!server.isSetMember()) { |
| 692 | // Fire all callback errors | |
| 693 | 0 | server.__executeAllCallbacksWithError(utils.toError(err)); |
| 694 | // Emit error | |
| 695 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); |
| 696 | } | |
| 697 | }); | |
| 698 | ||
| 699 | // Boot up connection poole, pass in a locator of callbacks | |
| 700 | 0 | connectionPool.start(); |
| 701 | } | |
| 702 | ||
| 703 | /** | |
| 704 | * @ignore | |
| 705 | */ | |
| 706 | 1 | Server.prototype.allRawConnections = function() { |
| 707 | 0 | return this.connectionPool != null ? this.connectionPool.getAllConnections() : []; |
| 708 | } | |
| 709 | ||
| 710 | /** | |
| 711 | * Check if a writer can be provided | |
| 712 | * @ignore | |
| 713 | */ | |
| 714 | 1 | var canCheckoutWriter = function(self, read) { |
| 715 | // We cannot write to an arbiter or secondary server | |
| 716 | 0 | if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) { |
| 717 | 0 | return new Error("Cannot write to an arbiter"); |
| 718 | 0 | } if(self.isMasterDoc && self.isMasterDoc['secondary'] == true) { |
| 719 | 0 | return new Error("Cannot write to a secondary"); |
| 720 | 0 | } else if(read == true && self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) { |
| 721 | 0 | return new Error("Cannot read from primary when secondary only specified"); |
| 722 | 0 | } else if(!self.isMasterDoc) { |
| 723 | 0 | return new Error("Cannot determine state of server"); |
| 724 | } | |
| 725 | ||
| 726 | // Return no error | |
| 727 | 0 | return null; |
| 728 | } | |
| 729 | ||
| 730 | /** | |
| 731 | * @ignore | |
| 732 | */ | |
| 733 | 1 | Server.prototype.checkoutWriter = function(read) { |
| 734 | 0 | if(read == true) return this.connectionPool.checkoutConnection(); |
| 735 | // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) | |
| 736 | 0 | var result = canCheckoutWriter(this, read); |
| 737 | // If the result is null check out a writer | |
| 738 | 0 | if(result == null && this.connectionPool != null) { |
| 739 | 0 | var connection = this.connectionPool.checkoutConnection(); |
| 740 | // Add server capabilities to the connection | |
| 741 | 0 | if(connection) |
| 742 | 0 | connection.serverCapabilities = this.serverCapabilities; |
| 743 | 0 | return connection; |
| 744 | 0 | } else if(result == null) { |
| 745 | 0 | return null; |
| 746 | } else { | |
| 747 | 0 | return result; |
| 748 | } | |
| 749 | } | |
| 750 | ||
| 751 | /** | |
| 752 | * Check if a reader can be provided | |
| 753 | * @ignore | |
| 754 | */ | |
| 755 | 1 | var canCheckoutReader = function(self) { |
| 756 | // We cannot write to an arbiter or secondary server | |
| 757 | 0 | if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) { |
| 758 | 0 | return new Error("Cannot write to an arbiter"); |
| 759 | 0 | } else if(self._readPreference != null) { |
| 760 | // If the read preference is Primary and the instance is not a master return an error | |
| 761 | 0 | if((self._readPreference == ReadPreference.PRIMARY) && self.isMasterDoc && self.isMasterDoc['ismaster'] != true) { |
| 762 | 0 | return new Error("Read preference is Server.PRIMARY and server is not master"); |
| 763 | 0 | } else if(self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) { |
| 764 | 0 | return new Error("Cannot read from primary when secondary only specified"); |
| 765 | } | |
| 766 | 0 | } else if(!self.isMasterDoc) { |
| 767 | 0 | return new Error("Cannot determine state of server"); |
| 768 | } | |
| 769 | ||
| 770 | // Return no error | |
| 771 | 0 | return null; |
| 772 | } | |
| 773 | ||
| 774 | /** | |
| 775 | * @ignore | |
| 776 | */ | |
| 777 | 1 | Server.prototype.checkoutReader = function(read) { |
| 778 | // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) | |
| 779 | 0 | var result = canCheckoutReader(this); |
| 780 | // If the result is null check out a writer | |
| 781 | 0 | if(result == null && this.connectionPool != null) { |
| 782 | 0 | var connection = this.connectionPool.checkoutConnection(); |
| 783 | // Add server capabilities to the connection | |
| 784 | 0 | if(connection) |
| 785 | 0 | connection.serverCapabilities = this.serverCapabilities; |
| 786 | 0 | return connection; |
| 787 | 0 | } else if(result == null) { |
| 788 | 0 | return null; |
| 789 | } else { | |
| 790 | 0 | return result; |
| 791 | } | |
| 792 | } | |
| 793 | ||
| 794 | /** | |
| 795 | * @ignore | |
| 796 | */ | |
| 797 | 1 | Server.prototype.enableRecordQueryStats = function(enable) { |
| 798 | 0 | this.recordQueryStats = enable; |
| 799 | } | |
| 800 | ||
| 801 | /** | |
| 802 | * Internal statistics object used for calculating average and standard devitation on | |
| 803 | * running queries | |
| 804 | * @ignore | |
| 805 | */ | |
| 806 | 1 | var RunningStats = function() { |
| 807 | 0 | var self = this; |
| 808 | 0 | this.m_n = 0; |
| 809 | 0 | this.m_oldM = 0.0; |
| 810 | 0 | this.m_oldS = 0.0; |
| 811 | 0 | this.m_newM = 0.0; |
| 812 | 0 | this.m_newS = 0.0; |
| 813 | ||
| 814 | // Define getters | |
| 815 | 0 | Object.defineProperty(this, "numDataValues", { enumerable: true |
| 816 | 0 | , get: function () { return this.m_n; } |
| 817 | }); | |
| 818 | ||
| 819 | 0 | Object.defineProperty(this, "mean", { enumerable: true |
| 820 | 0 | , get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; } |
| 821 | }); | |
| 822 | ||
| 823 | 0 | Object.defineProperty(this, "variance", { enumerable: true |
| 824 | 0 | , get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); } |
| 825 | }); | |
| 826 | ||
| 827 | 0 | Object.defineProperty(this, "standardDeviation", { enumerable: true |
| 828 | 0 | , get: function () { return Math.sqrt(this.variance); } |
| 829 | }); | |
| 830 | ||
| 831 | 0 | Object.defineProperty(this, "sScore", { enumerable: true |
| 832 | , get: function () { | |
| 833 | 0 | var bottom = this.mean + this.standardDeviation; |
| 834 | 0 | if(bottom == 0) return 0; |
| 835 | 0 | return ((2 * this.mean * this.standardDeviation)/(bottom)); |
| 836 | } | |
| 837 | }); | |
| 838 | } | |
| 839 | ||
| 840 | /** | |
| 841 | * @ignore | |
| 842 | */ | |
| 843 | 1 | RunningStats.prototype.push = function(x) { |
| 844 | // Update the number of samples | |
| 845 | 0 | this.m_n = this.m_n + 1; |
| 846 | // See Knuth TAOCP vol 2, 3rd edition, page 232 | |
| 847 | 0 | if(this.m_n == 1) { |
| 848 | 0 | this.m_oldM = this.m_newM = x; |
| 849 | 0 | this.m_oldS = 0.0; |
| 850 | } else { | |
| 851 | 0 | this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n; |
| 852 | 0 | this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM); |
| 853 | ||
| 854 | // set up for next iteration | |
| 855 | 0 | this.m_oldM = this.m_newM; |
| 856 | 0 | this.m_oldS = this.m_newS; |
| 857 | } | |
| 858 | } | |
| 859 | ||
| 860 | /** | |
| 861 | * @ignore | |
| 862 | */ | |
| 863 | 1 | Object.defineProperty(Server.prototype, "autoReconnect", { enumerable: true |
| 864 | , get: function () { | |
| 865 | 0 | return this.options['auto_reconnect'] == null ? false : this.options['auto_reconnect']; |
| 866 | } | |
| 867 | }); | |
| 868 | ||
| 869 | /** | |
| 870 | * @ignore | |
| 871 | */ | |
| 872 | 1 | Object.defineProperty(Server.prototype, "connection", { enumerable: true |
| 873 | , get: function () { | |
| 874 | 0 | return this.internalConnection; |
| 875 | } | |
| 876 | , set: function(connection) { | |
| 877 | 0 | this.internalConnection = connection; |
| 878 | } | |
| 879 | }); | |
| 880 | ||
| 881 | /** | |
| 882 | * @ignore | |
| 883 | */ | |
| 884 | 1 | Object.defineProperty(Server.prototype, "master", { enumerable: true |
| 885 | , get: function () { | |
| 886 | 0 | return this.internalMaster; |
| 887 | } | |
| 888 | , set: function(value) { | |
| 889 | 0 | this.internalMaster = value; |
| 890 | } | |
| 891 | }); | |
| 892 | ||
| 893 | /** | |
| 894 | * @ignore | |
| 895 | */ | |
| 896 | 1 | Object.defineProperty(Server.prototype, "primary", { enumerable: true |
| 897 | , get: function () { | |
| 898 | 0 | return this; |
| 899 | } | |
| 900 | }); | |
| 901 | ||
| 902 | /** | |
| 903 | * Getter for query Stats | |
| 904 | * @ignore | |
| 905 | */ | |
| 906 | 1 | Object.defineProperty(Server.prototype, "queryStats", { enumerable: true |
| 907 | , get: function () { | |
| 908 | 0 | return this._state.runtimeStats.queryStats; |
| 909 | } | |
| 910 | }); | |
| 911 | ||
| 912 | /** | |
| 913 | * @ignore | |
| 914 | */ | |
| 915 | 1 | Object.defineProperty(Server.prototype, "runtimeStats", { enumerable: true |
| 916 | , get: function () { | |
| 917 | 0 | return this._state.runtimeStats; |
| 918 | } | |
| 919 | }); | |
| 920 | ||
| 921 | /** | |
| 922 | * Get Read Preference method | |
| 923 | * @ignore | |
| 924 | */ | |
| 925 | 1 | Object.defineProperty(Server.prototype, "readPreference", { enumerable: true |
| 926 | , get: function () { | |
| 927 | 0 | if(this._readPreference == null && this.readSecondary) { |
| 928 | 0 | return Server.READ_SECONDARY; |
| 929 | 0 | } else if(this._readPreference == null && !this.readSecondary) { |
| 930 | 0 | return Server.READ_PRIMARY; |
| 931 | } else { | |
| 932 | 0 | return this._readPreference; |
| 933 | } | |
| 934 | } | |
| 935 | }); | |
| 936 | ||
| 937 | /** | |
| 938 | * @ignore | |
| 939 | */ | |
| 940 | 1 | exports.Server = Server; |
| 941 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ServerCapabilities = function(isMasterResult) { |
| 2 | // Capabilities | |
| 3 | 0 | var aggregationCursor = false; |
| 4 | 0 | var writeCommands = false; |
| 5 | 0 | var textSearch = false; |
| 6 | 0 | var authCommands = false; |
| 7 | 0 | var maxNumberOfDocsInBatch = 1000; |
| 8 | ||
| 9 | 0 | if(isMasterResult.minWireVersion >= 0) { |
| 10 | 0 | textSearch = true; |
| 11 | } | |
| 12 | ||
| 13 | 0 | if(isMasterResult.maxWireVersion >= 1) { |
| 14 | 0 | aggregationCursor = true; |
| 15 | 0 | authCommands = true; |
| 16 | } | |
| 17 | ||
| 18 | 0 | if(isMasterResult.maxWireVersion >= 2) { |
| 19 | 0 | writeCommands = true; |
| 20 | } | |
| 21 | ||
| 22 | // If no min or max wire version set to 0 | |
| 23 | 0 | if(isMasterResult.minWireVersion == null) { |
| 24 | 0 | isMasterResult.minWireVersion = 0; |
| 25 | } | |
| 26 | ||
| 27 | 0 | if(isMasterResult.maxWireVersion == null) { |
| 28 | 0 | isMasterResult.maxWireVersion = 0; |
| 29 | } | |
| 30 | ||
| 31 | // Map up read only parameters | |
| 32 | 0 | setup_get_property(this, "hasAggregationCursor", aggregationCursor); |
| 33 | 0 | setup_get_property(this, "hasWriteCommands", writeCommands); |
| 34 | 0 | setup_get_property(this, "hasTextSearch", textSearch); |
| 35 | 0 | setup_get_property(this, "hasAuthCommands", authCommands); |
| 36 | 0 | setup_get_property(this, "minWireVersion", isMasterResult.minWireVersion); |
| 37 | 0 | setup_get_property(this, "maxWireVersion", isMasterResult.maxWireVersion); |
| 38 | 0 | setup_get_property(this, "maxNumberOfDocsInBatch", maxNumberOfDocsInBatch); |
| 39 | } | |
| 40 | ||
| 41 | 1 | var setup_get_property = function(object, name, value) { |
| 42 | 0 | Object.defineProperty(object, name, { |
| 43 | enumerable: true | |
| 44 | 0 | , get: function () { return value; } |
| 45 | }); | |
| 46 | } | |
| 47 | ||
| 48 | 1 | exports.ServerCapabilities = ServerCapabilities; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var fs = require('fs'), |
| 2 | ReadPreference = require('./read_preference').ReadPreference; | |
| 3 | ||
| 4 | 1 | exports.parse = function(url, options) { |
| 5 | // Ensure we have a default options object if none set | |
| 6 | 0 | options = options || {}; |
| 7 | // Variables | |
| 8 | 0 | var connection_part = ''; |
| 9 | 0 | var auth_part = ''; |
| 10 | 0 | var query_string_part = ''; |
| 11 | 0 | var dbName = 'admin'; |
| 12 | ||
| 13 | // Must start with mongodb | |
| 14 | 0 | if(url.indexOf("mongodb://") != 0) |
| 15 | 0 | throw Error("URL must be in the format mongodb://user:pass@host:port/dbname"); |
| 16 | // If we have a ? mark cut the query elements off | |
| 17 | 0 | if(url.indexOf("?") != -1) { |
| 18 | 0 | query_string_part = url.substr(url.indexOf("?") + 1); |
| 19 | 0 | connection_part = url.substring("mongodb://".length, url.indexOf("?")) |
| 20 | } else { | |
| 21 | 0 | connection_part = url.substring("mongodb://".length); |
| 22 | } | |
| 23 | ||
| 24 | // Check if we have auth params | |
| 25 | 0 | if(connection_part.indexOf("@") != -1) { |
| 26 | 0 | auth_part = connection_part.split("@")[0]; |
| 27 | 0 | connection_part = connection_part.split("@")[1]; |
| 28 | } | |
| 29 | ||
| 30 | // Check if the connection string has a db | |
| 31 | 0 | if(connection_part.indexOf(".sock") != -1) { |
| 32 | 0 | if(connection_part.indexOf(".sock/") != -1) { |
| 33 | 0 | dbName = connection_part.split(".sock/")[1]; |
| 34 | 0 | connection_part = connection_part.split("/", connection_part.indexOf(".sock") + ".sock".length); |
| 35 | } | |
| 36 | 0 | } else if(connection_part.indexOf("/") != -1) { |
| 37 | 0 | dbName = connection_part.split("/")[1]; |
| 38 | 0 | connection_part = connection_part.split("/")[0]; |
| 39 | } | |
| 40 | ||
| 41 | // Result object | |
| 42 | 0 | var object = {}; |
| 43 | ||
| 44 | // Pick apart the authentication part of the string | |
| 45 | 0 | var authPart = auth_part || ''; |
| 46 | 0 | var auth = authPart.split(':', 2); |
| 47 | 0 | if(options['uri_decode_auth']){ |
| 48 | 0 | auth[0] = decodeURIComponent(auth[0]); |
| 49 | 0 | if(auth[1]){ |
| 50 | 0 | auth[1] = decodeURIComponent(auth[1]); |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | // Add auth to final object if we have 2 elements | |
| 55 | 0 | if(auth.length == 2) object.auth = {user: auth[0], password: auth[1]}; |
| 56 | ||
| 57 | // Variables used for temporary storage | |
| 58 | 0 | var hostPart; |
| 59 | 0 | var urlOptions; |
| 60 | 0 | var servers; |
| 61 | 0 | var serverOptions = {socketOptions: {}}; |
| 62 | 0 | var dbOptions = {read_preference_tags: []}; |
| 63 | 0 | var replSetServersOptions = {socketOptions: {}}; |
| 64 | // Add server options to final object | |
| 65 | 0 | object.server_options = serverOptions; |
| 66 | 0 | object.db_options = dbOptions; |
| 67 | 0 | object.rs_options = replSetServersOptions; |
| 68 | 0 | object.mongos_options = {}; |
| 69 | ||
| 70 | // Let's check if we are using a domain socket | |
| 71 | 0 | if(url.match(/\.sock/)) { |
| 72 | // Split out the socket part | |
| 73 | 0 | var domainSocket = url.substring( |
| 74 | url.indexOf("mongodb://") + "mongodb://".length | |
| 75 | , url.lastIndexOf(".sock") + ".sock".length); | |
| 76 | // Clean out any auth stuff if any | |
| 77 | 0 | if(domainSocket.indexOf("@") != -1) domainSocket = domainSocket.split("@")[1]; |
| 78 | 0 | servers = [{domain_socket: domainSocket}]; |
| 79 | } else { | |
| 80 | // Split up the db | |
| 81 | 0 | hostPart = connection_part; |
| 82 | // Parse all server results | |
| 83 | 0 | servers = hostPart.split(',').map(function(h) { |
| 84 | 0 | var hostPort = h.split(':', 2); |
| 85 | 0 | var _host = hostPort[0] || 'localhost'; |
| 86 | 0 | var _port = hostPort[1] != null ? parseInt(hostPort[1], 10) : 27017; |
| 87 | // Check for localhost?safe=true style case | |
| 88 | 0 | if(_host.indexOf("?") != -1) _host = _host.split(/\?/)[0]; |
| 89 | ||
| 90 | // Return the mapped object | |
| 91 | 0 | return {host: _host, port: _port}; |
| 92 | }); | |
| 93 | } | |
| 94 | ||
| 95 | // Get the db name | |
| 96 | 0 | object.dbName = dbName || 'admin'; |
| 97 | // Split up all the options | |
| 98 | 0 | urlOptions = (query_string_part || '').split(/[&;]/); |
| 99 | // Ugh, we have to figure out which options go to which constructor manually. | |
| 100 | 0 | urlOptions.forEach(function(opt) { |
| 101 | 0 | if(!opt) return; |
| 102 | 0 | var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1]; |
| 103 | // Options implementations | |
| 104 | 0 | switch(name) { |
| 105 | case 'slaveOk': | |
| 106 | case 'slave_ok': | |
| 107 | 0 | serverOptions.slave_ok = (value == 'true'); |
| 108 | 0 | dbOptions.slaveOk = (value == 'true'); |
| 109 | 0 | break; |
| 110 | case 'maxPoolSize': | |
| 111 | case 'poolSize': | |
| 112 | 0 | serverOptions.poolSize = parseInt(value, 10); |
| 113 | 0 | replSetServersOptions.poolSize = parseInt(value, 10); |
| 114 | 0 | break; |
| 115 | case 'autoReconnect': | |
| 116 | case 'auto_reconnect': | |
| 117 | 0 | serverOptions.auto_reconnect = (value == 'true'); |
| 118 | 0 | break; |
| 119 | case 'minPoolSize': | |
| 120 | 0 | throw new Error("minPoolSize not supported"); |
| 121 | case 'maxIdleTimeMS': | |
| 122 | 0 | throw new Error("maxIdleTimeMS not supported"); |
| 123 | case 'waitQueueMultiple': | |
| 124 | 0 | throw new Error("waitQueueMultiple not supported"); |
| 125 | case 'waitQueueTimeoutMS': | |
| 126 | 0 | throw new Error("waitQueueTimeoutMS not supported"); |
| 127 | case 'uuidRepresentation': | |
| 128 | 0 | throw new Error("uuidRepresentation not supported"); |
| 129 | case 'ssl': | |
| 130 | 0 | if(value == 'prefer') { |
| 131 | 0 | serverOptions.ssl = value; |
| 132 | 0 | replSetServersOptions.ssl = value; |
| 133 | 0 | break; |
| 134 | } | |
| 135 | 0 | serverOptions.ssl = (value == 'true'); |
| 136 | 0 | replSetServersOptions.ssl = (value == 'true'); |
| 137 | 0 | break; |
| 138 | case 'replicaSet': | |
| 139 | case 'rs_name': | |
| 140 | 0 | replSetServersOptions.rs_name = value; |
| 141 | 0 | break; |
| 142 | case 'reconnectWait': | |
| 143 | 0 | replSetServersOptions.reconnectWait = parseInt(value, 10); |
| 144 | 0 | break; |
| 145 | case 'retries': | |
| 146 | 0 | replSetServersOptions.retries = parseInt(value, 10); |
| 147 | 0 | break; |
| 148 | case 'readSecondary': | |
| 149 | case 'read_secondary': | |
| 150 | 0 | replSetServersOptions.read_secondary = (value == 'true'); |
| 151 | 0 | break; |
| 152 | case 'fsync': | |
| 153 | 0 | dbOptions.fsync = (value == 'true'); |
| 154 | 0 | break; |
| 155 | case 'journal': | |
| 156 | 0 | dbOptions.journal = (value == 'true'); |
| 157 | 0 | break; |
| 158 | case 'safe': | |
| 159 | 0 | dbOptions.safe = (value == 'true'); |
| 160 | 0 | break; |
| 161 | case 'nativeParser': | |
| 162 | case 'native_parser': | |
| 163 | 0 | dbOptions.native_parser = (value == 'true'); |
| 164 | 0 | break; |
| 165 | case 'connectTimeoutMS': | |
| 166 | 0 | serverOptions.socketOptions.connectTimeoutMS = parseInt(value, 10); |
| 167 | 0 | replSetServersOptions.socketOptions.connectTimeoutMS = parseInt(value, 10); |
| 168 | 0 | break; |
| 169 | case 'socketTimeoutMS': | |
| 170 | 0 | serverOptions.socketOptions.socketTimeoutMS = parseInt(value, 10); |
| 171 | 0 | replSetServersOptions.socketOptions.socketTimeoutMS = parseInt(value, 10); |
| 172 | 0 | break; |
| 173 | case 'w': | |
| 174 | 0 | dbOptions.w = parseInt(value, 10); |
| 175 | 0 | break; |
| 176 | case 'authSource': | |
| 177 | 0 | dbOptions.authSource = value; |
| 178 | 0 | break; |
| 179 | case 'gssapiServiceName': | |
| 180 | 0 | dbOptions.gssapiServiceName = value; |
| 181 | 0 | break; |
| 182 | case 'authMechanism': | |
| 183 | 0 | if(value == 'GSSAPI') { |
| 184 | // If no password provided decode only the principal | |
| 185 | 0 | if(object.auth == null) { |
| 186 | 0 | var urlDecodeAuthPart = decodeURIComponent(authPart); |
| 187 | 0 | if(urlDecodeAuthPart.indexOf("@") == -1) throw new Error("GSSAPI requires a provided principal"); |
| 188 | 0 | object.auth = {user: urlDecodeAuthPart, password: null}; |
| 189 | } else { | |
| 190 | 0 | object.auth.user = decodeURIComponent(object.auth.user); |
| 191 | } | |
| 192 | 0 | } else if(value == 'MONGODB-X509') { |
| 193 | 0 | object.auth = {user: decodeURIComponent(authPart)}; |
| 194 | } | |
| 195 | ||
| 196 | // Only support GSSAPI or MONGODB-CR for now | |
| 197 | 0 | if(value != 'GSSAPI' |
| 198 | && value != 'MONGODB-X509' | |
| 199 | && value != 'MONGODB-CR' | |
| 200 | && value != 'PLAIN') | |
| 201 | 0 | throw new Error("only GSSAPI, PLAIN, MONGODB-X509 or MONGODB-CR is supported by authMechanism"); |
| 202 | ||
| 203 | // Authentication mechanism | |
| 204 | 0 | dbOptions.authMechanism = value; |
| 205 | 0 | break; |
| 206 | case 'wtimeoutMS': | |
| 207 | 0 | dbOptions.wtimeoutMS = parseInt(value, 10); |
| 208 | 0 | break; |
| 209 | case 'readPreference': | |
| 210 | 0 | if(!ReadPreference.isValid(value)) throw new Error("readPreference must be either primary/primaryPreferred/secondary/secondaryPreferred/nearest"); |
| 211 | 0 | dbOptions.read_preference = value; |
| 212 | 0 | break; |
| 213 | case 'readPreferenceTags': | |
| 214 | // Contains the tag object | |
| 215 | 0 | var tagObject = {}; |
| 216 | 0 | if(value == null || value == '') { |
| 217 | 0 | dbOptions.read_preference_tags.push(tagObject); |
| 218 | 0 | break; |
| 219 | } | |
| 220 | ||
| 221 | // Split up the tags | |
| 222 | 0 | var tags = value.split(/\,/); |
| 223 | 0 | for(var i = 0; i < tags.length; i++) { |
| 224 | 0 | var parts = tags[i].trim().split(/\:/); |
| 225 | 0 | tagObject[parts[0]] = parts[1]; |
| 226 | } | |
| 227 | ||
| 228 | // Set the preferences tags | |
| 229 | 0 | dbOptions.read_preference_tags.push(tagObject); |
| 230 | 0 | break; |
| 231 | default: | |
| 232 | 0 | break; |
| 233 | } | |
| 234 | }); | |
| 235 | ||
| 236 | // No tags: should be null (not []) | |
| 237 | 0 | if(dbOptions.read_preference_tags.length === 0) { |
| 238 | 0 | dbOptions.read_preference_tags = null; |
| 239 | } | |
| 240 | ||
| 241 | // Validate if there are an invalid write concern combinations | |
| 242 | 0 | if((dbOptions.w == -1 || dbOptions.w == 0) && ( |
| 243 | dbOptions.journal == true | |
| 244 | || dbOptions.fsync == true | |
| 245 | 0 | || dbOptions.safe == true)) throw new Error("w set to -1 or 0 cannot be combined with safe/w/journal/fsync") |
| 246 | ||
| 247 | // If no read preference set it to primary | |
| 248 | 0 | if(!dbOptions.read_preference) dbOptions.read_preference = 'primary'; |
| 249 | ||
| 250 | // Add servers to result | |
| 251 | 0 | object.servers = servers; |
| 252 | // Returned parsed object | |
| 253 | 0 | return object; |
| 254 | } | |
| 255 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var QueryCommand = require('./commands/query_command').QueryCommand, |
| 2 | GetMoreCommand = require('./commands/get_more_command').GetMoreCommand, | |
| 3 | KillCursorCommand = require('./commands/kill_cursor_command').KillCursorCommand, | |
| 4 | Long = require('bson').Long, | |
| 5 | ReadPreference = require('./connection/read_preference').ReadPreference, | |
| 6 | CursorStream = require('./cursorstream'), | |
| 7 | timers = require('timers'), | |
| 8 | utils = require('./utils'); | |
| 9 | ||
| 10 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 11 | 1 | var processor = require('./utils').processor(); |
| 12 | ||
| 13 | /** | |
| 14 | * Constructor for a cursor object that handles all the operations on query result | |
| 15 | * using find. This cursor object is unidirectional and cannot traverse backwards. Clients should not be creating a cursor directly, | |
| 16 | * but use find to acquire a cursor. (INTERNAL TYPE) | |
| 17 | * | |
| 18 | * Options | |
| 19 | * - **skip** {Number} skip number of documents to skip. | |
| 20 | * - **limit** {Number}, limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. | |
| 21 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 22 | * - **hint** {Object}, hint force the query to use a specific index. | |
| 23 | * - **explain** {Boolean}, explain return the explaination of the query. | |
| 24 | * - **snapshot** {Boolean}, snapshot Snapshot mode assures no duplicates are returned. | |
| 25 | * - **timeout** {Boolean}, timeout allow the query to timeout. | |
| 26 | * - **tailable** {Boolean}, tailable allow the cursor to be tailable. | |
| 27 | * - **awaitdata** {Boolean}, awaitdata allow the cursor to wait for data, only applicable for tailable cursor. | |
| 28 | * - **oplogReplay** {Boolean}, sets an internal flag, only applicable for tailable cursor. | |
| 29 | * - **batchSize** {Number}, batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. | |
| 30 | * - **raw** {Boolean}, raw return all query documents as raw buffers (default false). | |
| 31 | * - **read** {Boolean}, read specify override of read from source (primary/secondary). | |
| 32 | * - **returnKey** {Boolean}, returnKey only return the index key. | |
| 33 | * - **maxScan** {Number}, maxScan limit the number of items to scan. | |
| 34 | * - **min** {Number}, min set index bounds. | |
| 35 | * - **max** {Number}, max set index bounds. | |
| 36 | * - **maxTimeMS** {Number}, number of miliseconds to wait before aborting the query. | |
| 37 | * - **showDiskLoc** {Boolean}, showDiskLoc show disk location of results. | |
| 38 | * - **comment** {String}, comment you can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 39 | * - **numberOfRetries** {Number}, numberOfRetries if using awaidata specifies the number of times to retry on timeout. | |
| 40 | * - **dbName** {String}, dbName override the default dbName. | |
| 41 | * - **tailableRetryInterval** {Number}, tailableRetryInterval specify the miliseconds between getMores on tailable cursor. | |
| 42 | * - **exhaust** {Boolean}, exhaust have the server send all the documents at once as getMore packets. | |
| 43 | * - **partial** {Boolean}, partial have the sharded system return a partial result from mongos. | |
| 44 | * | |
| 45 | * @class Represents a Cursor. | |
| 46 | * @param {Db} db the database object to work with. | |
| 47 | * @param {Collection} collection the collection to query. | |
| 48 | * @param {Object} selector the query selector. | |
| 49 | * @param {Object} fields an object containing what fields to include or exclude from objects returned. | |
| 50 | * @param {Object} [options] additional options for the collection. | |
| 51 | */ | |
| 52 | 1 | function Cursor(db, collection, selector, fields, options) { |
| 53 | 0 | this.db = db; |
| 54 | 0 | this.collection = collection; |
| 55 | 0 | this.selector = selector; |
| 56 | 0 | this.fields = fields; |
| 57 | 0 | options = !options ? {} : options; |
| 58 | ||
| 59 | 0 | this.skipValue = options.skip == null ? 0 : options.skip; |
| 60 | 0 | this.limitValue = options.limit == null ? 0 : options.limit; |
| 61 | 0 | this.sortValue = options.sort; |
| 62 | 0 | this.hint = options.hint; |
| 63 | 0 | this.explainValue = options.explain; |
| 64 | 0 | this.snapshot = options.snapshot; |
| 65 | 0 | this.timeout = options.timeout == null ? true : options.timeout; |
| 66 | 0 | this.tailable = options.tailable; |
| 67 | 0 | this.awaitdata = options.awaitdata; |
| 68 | 0 | this.oplogReplay = options.oplogReplay; |
| 69 | 0 | this.numberOfRetries = options.numberOfRetries == null ? 5 : options.numberOfRetries; |
| 70 | 0 | this.currentNumberOfRetries = this.numberOfRetries; |
| 71 | 0 | this.batchSizeValue = options.batchSize == null ? 0 : options.batchSize; |
| 72 | 0 | this.raw = options.raw == null ? false : options.raw; |
| 73 | 0 | this.read = options.read == null ? ReadPreference.PRIMARY : options.read; |
| 74 | 0 | this.returnKey = options.returnKey; |
| 75 | 0 | this.maxScan = options.maxScan; |
| 76 | 0 | this.min = options.min; |
| 77 | 0 | this.max = options.max; |
| 78 | 0 | this.showDiskLoc = options.showDiskLoc; |
| 79 | 0 | this.comment = options.comment; |
| 80 | 0 | this.tailableRetryInterval = options.tailableRetryInterval || 100; |
| 81 | 0 | this.exhaust = options.exhaust || false; |
| 82 | 0 | this.partial = options.partial || false; |
| 83 | 0 | this.slaveOk = options.slaveOk || false; |
| 84 | 0 | this.maxTimeMSValue = options.maxTimeMS; |
| 85 | ||
| 86 | 0 | this.totalNumberOfRecords = 0; |
| 87 | 0 | this.items = []; |
| 88 | 0 | this.cursorId = Long.fromInt(0); |
| 89 | ||
| 90 | // This name | |
| 91 | 0 | this.dbName = options.dbName; |
| 92 | ||
| 93 | // State variables for the cursor | |
| 94 | 0 | this.state = Cursor.INIT; |
| 95 | // Keep track of the current query run | |
| 96 | 0 | this.queryRun = false; |
| 97 | 0 | this.getMoreTimer = false; |
| 98 | ||
| 99 | // If we are using a specific db execute against it | |
| 100 | 0 | if(this.dbName != null) { |
| 101 | 0 | this.collectionName = this.dbName + "." + this.collection.collectionName; |
| 102 | } else { | |
| 103 | 0 | this.collectionName = (this.db.databaseName ? this.db.databaseName + "." : '') + this.collection.collectionName; |
| 104 | } | |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Resets this cursor to its initial state. All settings like the query string, | |
| 109 | * tailable, batchSizeValue, skipValue and limits are preserved. | |
| 110 | * | |
| 111 | * @return {Cursor} returns itself with rewind applied. | |
| 112 | * @api public | |
| 113 | */ | |
| 114 | 1 | Cursor.prototype.rewind = function() { |
| 115 | 0 | var self = this; |
| 116 | ||
| 117 | 0 | if (self.state != Cursor.INIT) { |
| 118 | 0 | if (self.state != Cursor.CLOSED) { |
| 119 | 0 | self.close(function() {}); |
| 120 | } | |
| 121 | ||
| 122 | 0 | self.numberOfReturned = 0; |
| 123 | 0 | self.totalNumberOfRecords = 0; |
| 124 | 0 | self.items = []; |
| 125 | 0 | self.cursorId = Long.fromInt(0); |
| 126 | 0 | self.state = Cursor.INIT; |
| 127 | 0 | self.queryRun = false; |
| 128 | } | |
| 129 | ||
| 130 | 0 | return self; |
| 131 | }; | |
| 132 | ||
| 133 | ||
| 134 | /** | |
| 135 | * Returns an array of documents. The caller is responsible for making sure that there | |
| 136 | * is enough memory to store the results. Note that the array only contain partial | |
| 137 | * results when this cursor had been previouly accessed. In that case, | |
| 138 | * cursor.rewind() can be used to reset the cursor. | |
| 139 | * | |
| 140 | * @param {Function} callback This will be called after executing this method successfully. The first parameter will contain the Error object if an error occured, or null otherwise. The second parameter will contain an array of BSON deserialized objects as a result of the query. | |
| 141 | * @return {null} | |
| 142 | * @api public | |
| 143 | */ | |
| 144 | 1 | Cursor.prototype.toArray = function(callback) { |
| 145 | 0 | var self = this; |
| 146 | ||
| 147 | 0 | if(!callback) { |
| 148 | 0 | throw new Error('callback is mandatory'); |
| 149 | } | |
| 150 | ||
| 151 | 0 | if(this.tailable) { |
| 152 | 0 | callback(new Error("Tailable cursor cannot be converted to array"), null); |
| 153 | 0 | } else if(this.state != Cursor.CLOSED) { |
| 154 | // return toArrayExhaust(self, callback); | |
| 155 | // If we are using exhaust we can't use the quick fire method | |
| 156 | 0 | if(self.exhaust) return toArrayExhaust(self, callback); |
| 157 | // Quick fire using trampoline to avoid nextTick | |
| 158 | 0 | self.nextObject({noReturn: true}, function(err, result) { |
| 159 | 0 | if(err) return callback(utils.toError(err), null); |
| 160 | 0 | if(self.cursorId.toString() == "0") { |
| 161 | 0 | self.state = Cursor.CLOSED; |
| 162 | 0 | return callback(null, self.items); |
| 163 | } | |
| 164 | ||
| 165 | // Let's issue getMores until we have no more records waiting | |
| 166 | 0 | getAllByGetMore(self, function(err, done) { |
| 167 | 0 | self.state = Cursor.CLOSED; |
| 168 | 0 | if(err) return callback(utils.toError(err), null); |
| 169 | // Let's release the internal list | |
| 170 | 0 | var items = self.items; |
| 171 | 0 | self.items = null; |
| 172 | // Return all the items | |
| 173 | 0 | callback(null, items); |
| 174 | }); | |
| 175 | }) | |
| 176 | ||
| 177 | } else { | |
| 178 | 0 | callback(new Error("Cursor is closed"), null); |
| 179 | } | |
| 180 | } | |
| 181 | ||
| 182 | 1 | var toArrayExhaust = function(self, callback) { |
| 183 | 0 | var items = []; |
| 184 | ||
| 185 | 0 | self.each(function(err, item) { |
| 186 | 0 | if(err != null) { |
| 187 | 0 | return callback(utils.toError(err), null); |
| 188 | } | |
| 189 | ||
| 190 | 0 | if(item != null && Array.isArray(items)) { |
| 191 | 0 | items.push(item); |
| 192 | } else { | |
| 193 | 0 | var resultItems = items; |
| 194 | 0 | items = null; |
| 195 | 0 | self.items = []; |
| 196 | 0 | callback(null, resultItems); |
| 197 | } | |
| 198 | }); | |
| 199 | } | |
| 200 | ||
| 201 | 1 | var getAllByGetMore = function(self, callback) { |
| 202 | 0 | getMore(self, {noReturn: true}, function(err, result) { |
| 203 | 0 | if(err) return callback(utils.toError(err)); |
| 204 | 0 | if(result == null) return callback(null, null); |
| 205 | 0 | if(self.cursorId.toString() == "0") return callback(null, null); |
| 206 | 0 | getAllByGetMore(self, callback); |
| 207 | }) | |
| 208 | }; | |
| 209 | ||
| 210 | /** | |
| 211 | * Iterates over all the documents for this cursor. As with **{cursor.toArray}**, | |
| 212 | * not all of the elements will be iterated if this cursor had been previouly accessed. | |
| 213 | * In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike | |
| 214 | * **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements | |
| 215 | * at any given time if batch size is specified. Otherwise, the caller is responsible | |
| 216 | * for making sure that the entire result can fit the memory. | |
| 217 | * | |
| 218 | * @param {Function} callback this will be called for while iterating every document of the query result. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the document. | |
| 219 | * @return {null} | |
| 220 | * @api public | |
| 221 | */ | |
| 222 | 1 | Cursor.prototype.each = function(callback) { |
| 223 | 0 | var self = this; |
| 224 | 0 | var fn; |
| 225 | ||
| 226 | 0 | if (!callback) { |
| 227 | 0 | throw new Error('callback is mandatory'); |
| 228 | } | |
| 229 | ||
| 230 | 0 | if(this.state != Cursor.CLOSED) { |
| 231 | // If we are using exhaust we can't use the quick fire method | |
| 232 | 0 | if(self.exhaust) return eachExhaust(self, callback); |
| 233 | // Quick fire using trampoline to avoid nextTick | |
| 234 | 0 | if(this.items.length > 0) { |
| 235 | // Trampoline all the entries | |
| 236 | 0 | while(fn = loop(self, callback)) fn(self, callback); |
| 237 | // Call each again | |
| 238 | 0 | self.each(callback); |
| 239 | } else { | |
| 240 | 0 | self.nextObject(function(err, item) { |
| 241 | ||
| 242 | 0 | if(err) { |
| 243 | 0 | self.state = Cursor.CLOSED; |
| 244 | 0 | return callback(utils.toError(err), item); |
| 245 | } | |
| 246 | ||
| 247 | 0 | if(item == null) return callback(null, null); |
| 248 | 0 | callback(null, item); |
| 249 | 0 | self.each(callback); |
| 250 | }) | |
| 251 | } | |
| 252 | } else { | |
| 253 | 0 | callback(new Error("Cursor is closed"), null); |
| 254 | } | |
| 255 | }; | |
| 256 | ||
| 257 | // Special for exhaust command as we don't initiate the actual result sets | |
| 258 | // the server just sends them as they arrive meaning we need to get the IO event | |
| 259 | // loop happen so we can receive more data from the socket or we return to early | |
| 260 | // after the first fetch and loose all the incoming getMore's automatically issued | |
| 261 | // from the server. | |
| 262 | 1 | var eachExhaust = function(self, callback) { |
| 263 | //FIX: stack overflow (on deep callback) (cred: https://github.com/limp/node-mongodb-native/commit/27da7e4b2af02035847f262b29837a94bbbf6ce2) | |
| 264 | 0 | processor(function(){ |
| 265 | // Fetch the next object until there is no more objects | |
| 266 | 0 | self.nextObject(function(err, item) { |
| 267 | 0 | if(err != null) return callback(err, null); |
| 268 | 0 | if(item != null) { |
| 269 | 0 | callback(null, item); |
| 270 | 0 | eachExhaust(self, callback); |
| 271 | } else { | |
| 272 | // Close the cursor if done | |
| 273 | 0 | self.state = Cursor.CLOSED; |
| 274 | 0 | callback(err, null); |
| 275 | } | |
| 276 | }); | |
| 277 | }); | |
| 278 | } | |
| 279 | ||
| 280 | // Trampoline emptying the number of retrieved items | |
| 281 | // without incurring a nextTick operation | |
| 282 | 1 | var loop = function(self, callback) { |
| 283 | // No more items we are done | |
| 284 | 0 | if(self.items.length == 0) return; |
| 285 | // Get the next document | |
| 286 | 0 | var doc = self.items.shift(); |
| 287 | // Callback | |
| 288 | 0 | callback(null, doc); |
| 289 | // Loop | |
| 290 | 0 | return loop; |
| 291 | } | |
| 292 | ||
| 293 | /** | |
| 294 | * Determines how many result the query for this cursor will return | |
| 295 | * | |
| 296 | * @param {Boolean} applySkipLimit if set to true will apply the skip and limits set on the cursor. Defaults to false. | |
| 297 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the number of results or null if an error occured. | |
| 298 | * @return {null} | |
| 299 | * @api public | |
| 300 | */ | |
| 301 | 1 | Cursor.prototype.count = function(applySkipLimit, callback) { |
| 302 | 0 | if(typeof applySkipLimit == 'function') { |
| 303 | 0 | callback = applySkipLimit; |
| 304 | 0 | applySkipLimit = false; |
| 305 | } | |
| 306 | ||
| 307 | 0 | var options = {}; |
| 308 | 0 | if(applySkipLimit) { |
| 309 | 0 | if(typeof this.skipValue == 'number') options.skip = this.skipValue; |
| 310 | 0 | if(typeof this.limitValue == 'number') options.limit = this.limitValue; |
| 311 | } | |
| 312 | ||
| 313 | // If maxTimeMS set | |
| 314 | 0 | if(typeof this.maxTimeMSValue == 'number') options.maxTimeMS = this.maxTimeMSValue; |
| 315 | ||
| 316 | // Call count command | |
| 317 | 0 | this.collection.count(this.selector, options, callback); |
| 318 | }; | |
| 319 | ||
| 320 | /** | |
| 321 | * Sets the sort parameter of this cursor to the given value. | |
| 322 | * | |
| 323 | * This method has the following method signatures: | |
| 324 | * (keyOrList, callback) | |
| 325 | * (keyOrList, direction, callback) | |
| 326 | * | |
| 327 | * @param {String|Array|Object} keyOrList This can be a string or an array. If passed as a string, the string will be the field to sort. If passed an array, each element will represent a field to be sorted and should be an array that contains the format [string, direction]. | |
| 328 | * @param {String|Number} direction this determines how the results are sorted. "asc", "ascending" or 1 for asceding order while "desc", "desceding or -1 for descending order. Note that the strings are case insensitive. | |
| 329 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 330 | * @return {Cursor} an instance of this object. | |
| 331 | * @api public | |
| 332 | */ | |
| 333 | 1 | Cursor.prototype.sort = function(keyOrList, direction, callback) { |
| 334 | 0 | callback = callback || function(){}; |
| 335 | 0 | if(typeof direction === "function") { callback = direction; direction = null; } |
| 336 | ||
| 337 | 0 | if(this.tailable) { |
| 338 | 0 | callback(new Error("Tailable cursor doesn't support sorting"), null); |
| 339 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 340 | 0 | callback(new Error("Cursor is closed"), null); |
| 341 | } else { | |
| 342 | 0 | var order = keyOrList; |
| 343 | ||
| 344 | 0 | if(direction != null) { |
| 345 | 0 | order = [[keyOrList, direction]]; |
| 346 | } | |
| 347 | ||
| 348 | 0 | this.sortValue = order; |
| 349 | 0 | callback(null, this); |
| 350 | } | |
| 351 | 0 | return this; |
| 352 | }; | |
| 353 | ||
| 354 | /** | |
| 355 | * Sets the limit parameter of this cursor to the given value. | |
| 356 | * | |
| 357 | * @param {Number} limit the new limit. | |
| 358 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 359 | * @return {Cursor} an instance of this object. | |
| 360 | * @api public | |
| 361 | */ | |
| 362 | 1 | Cursor.prototype.limit = function(limit, callback) { |
| 363 | 0 | if(this.tailable) { |
| 364 | 0 | if(callback) { |
| 365 | 0 | callback(new Error("Tailable cursor doesn't support limit"), null); |
| 366 | } else { | |
| 367 | 0 | throw new Error("Tailable cursor doesn't support limit"); |
| 368 | } | |
| 369 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 370 | 0 | if(callback) { |
| 371 | 0 | callback(new Error("Cursor is closed"), null); |
| 372 | } else { | |
| 373 | 0 | throw new Error("Cursor is closed"); |
| 374 | } | |
| 375 | } else { | |
| 376 | 0 | if(limit != null && limit.constructor != Number) { |
| 377 | 0 | if(callback) { |
| 378 | 0 | callback(new Error("limit requires an integer"), null); |
| 379 | } else { | |
| 380 | 0 | throw new Error("limit requires an integer"); |
| 381 | } | |
| 382 | } else { | |
| 383 | 0 | this.limitValue = limit; |
| 384 | 0 | if(callback) return callback(null, this); |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | 0 | return this; |
| 389 | }; | |
| 390 | ||
| 391 | /** | |
| 392 | * Specifies a time limit for a query operation. After the specified | |
| 393 | * time is exceeded, the operation will be aborted and an error will be | |
| 394 | * returned to the client. If maxTimeMS is null, no limit is applied. | |
| 395 | * | |
| 396 | * @param {Number} maxTimeMS the maxTimeMS for the query. | |
| 397 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 398 | * @return {Cursor} an instance of this object. | |
| 399 | * @api public | |
| 400 | */ | |
| 401 | 1 | Cursor.prototype.maxTimeMS = function(maxTimeMS, callback) { |
| 402 | 0 | if(typeof maxTimeMS != 'number') { |
| 403 | 0 | throw new Error("maxTimeMS must be a number"); |
| 404 | } | |
| 405 | ||
| 406 | // Save the maxTimeMS option | |
| 407 | 0 | this.maxTimeMSValue = maxTimeMS; |
| 408 | // Return the cursor for chaining | |
| 409 | 0 | return this; |
| 410 | }; | |
| 411 | ||
| 412 | /** | |
| 413 | * Sets the read preference for the cursor | |
| 414 | * | |
| 415 | * @param {String} the read preference for the cursor, one of Server.READ_PRIMARY, Server.READ_SECONDARY, Server.READ_SECONDARY_ONLY | |
| 416 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the read preference given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 417 | * @return {Cursor} an instance of this object. | |
| 418 | * @api public | |
| 419 | */ | |
| 420 | 1 | Cursor.prototype.setReadPreference = function(readPreference, tags, callback) { |
| 421 | 0 | if(typeof tags == 'function') callback = tags; |
| 422 | ||
| 423 | 0 | var _mode = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; |
| 424 | ||
| 425 | 0 | if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 426 | 0 | if(callback == null) throw new Error("Cannot change read preference on executed query or closed cursor"); |
| 427 | 0 | callback(new Error("Cannot change read preference on executed query or closed cursor")); |
| 428 | 0 | } else if(_mode != null && _mode != 'primary' |
| 429 | && _mode != 'secondaryOnly' && _mode != 'secondary' | |
| 430 | && _mode != 'nearest' && _mode != 'primaryPreferred' && _mode != 'secondaryPreferred') { | |
| 431 | 0 | if(callback == null) throw new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported"); |
| 432 | 0 | callback(new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported")); |
| 433 | } else { | |
| 434 | 0 | this.read = readPreference; |
| 435 | 0 | if(callback != null) callback(null, this); |
| 436 | } | |
| 437 | ||
| 438 | 0 | return this; |
| 439 | } | |
| 440 | ||
| 441 | /** | |
| 442 | * Sets the skip parameter of this cursor to the given value. | |
| 443 | * | |
| 444 | * @param {Number} skip the new skip value. | |
| 445 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the skip value given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 446 | * @return {Cursor} an instance of this object. | |
| 447 | * @api public | |
| 448 | */ | |
| 449 | 1 | Cursor.prototype.skip = function(skip, callback) { |
| 450 | 0 | callback = callback || function(){}; |
| 451 | ||
| 452 | 0 | if(this.tailable) { |
| 453 | 0 | callback(new Error("Tailable cursor doesn't support skip"), null); |
| 454 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 455 | 0 | callback(new Error("Cursor is closed"), null); |
| 456 | } else { | |
| 457 | 0 | if(skip != null && skip.constructor != Number) { |
| 458 | 0 | callback(new Error("skip requires an integer"), null); |
| 459 | } else { | |
| 460 | 0 | this.skipValue = skip; |
| 461 | 0 | callback(null, this); |
| 462 | } | |
| 463 | } | |
| 464 | ||
| 465 | 0 | return this; |
| 466 | }; | |
| 467 | ||
| 468 | /** | |
| 469 | * Sets the batch size parameter of this cursor to the given value. | |
| 470 | * | |
| 471 | * @param {Number} batchSize the new batch size. | |
| 472 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the batchSize given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 473 | * @return {Cursor} an instance of this object. | |
| 474 | * @api public | |
| 475 | */ | |
| 476 | 1 | Cursor.prototype.batchSize = function(batchSize, callback) { |
| 477 | 0 | if(this.state == Cursor.CLOSED) { |
| 478 | 0 | if(callback != null) { |
| 479 | 0 | return callback(new Error("Cursor is closed"), null); |
| 480 | } else { | |
| 481 | 0 | throw new Error("Cursor is closed"); |
| 482 | } | |
| 483 | 0 | } else if(batchSize != null && batchSize.constructor != Number) { |
| 484 | 0 | if(callback != null) { |
| 485 | 0 | return callback(new Error("batchSize requires an integer"), null); |
| 486 | } else { | |
| 487 | 0 | throw new Error("batchSize requires an integer"); |
| 488 | } | |
| 489 | } else { | |
| 490 | 0 | this.batchSizeValue = batchSize; |
| 491 | 0 | if(callback != null) return callback(null, this); |
| 492 | } | |
| 493 | ||
| 494 | 0 | return this; |
| 495 | }; | |
| 496 | ||
| 497 | /** | |
| 498 | * The limit used for the getMore command | |
| 499 | * | |
| 500 | * @return {Number} The number of records to request per batch. | |
| 501 | * @ignore | |
| 502 | * @api private | |
| 503 | */ | |
| 504 | 1 | var limitRequest = function(self) { |
| 505 | 0 | var requestedLimit = self.limitValue; |
| 506 | 0 | var absLimitValue = Math.abs(self.limitValue); |
| 507 | 0 | var absBatchValue = Math.abs(self.batchSizeValue); |
| 508 | ||
| 509 | 0 | if(absLimitValue > 0) { |
| 510 | 0 | if (absBatchValue > 0) { |
| 511 | 0 | requestedLimit = Math.min(absLimitValue, absBatchValue); |
| 512 | } | |
| 513 | } else { | |
| 514 | 0 | requestedLimit = self.batchSizeValue; |
| 515 | } | |
| 516 | ||
| 517 | 0 | return requestedLimit; |
| 518 | }; | |
| 519 | ||
| 520 | ||
| 521 | /** | |
| 522 | * Generates a QueryCommand object using the parameters of this cursor. | |
| 523 | * | |
| 524 | * @return {QueryCommand} The command object | |
| 525 | * @ignore | |
| 526 | * @api private | |
| 527 | */ | |
| 528 | 1 | var generateQueryCommand = function(self) { |
| 529 | // Unpack the options | |
| 530 | 0 | var queryOptions = QueryCommand.OPTS_NONE; |
| 531 | 0 | if(!self.timeout) { |
| 532 | 0 | queryOptions |= QueryCommand.OPTS_NO_CURSOR_TIMEOUT; |
| 533 | } | |
| 534 | ||
| 535 | 0 | if(self.tailable != null) { |
| 536 | 0 | queryOptions |= QueryCommand.OPTS_TAILABLE_CURSOR; |
| 537 | 0 | self.skipValue = self.limitValue = 0; |
| 538 | ||
| 539 | // if awaitdata is set | |
| 540 | 0 | if(self.awaitdata != null) { |
| 541 | 0 | queryOptions |= QueryCommand.OPTS_AWAIT_DATA; |
| 542 | } | |
| 543 | ||
| 544 | // This sets an internal undocumented flag. Clients should not depend on its | |
| 545 | // behavior! | |
| 546 | 0 | if(self.oplogReplay != null) { |
| 547 | 0 | queryOptions |= QueryCommand.OPTS_OPLOG_REPLAY; |
| 548 | } | |
| 549 | } | |
| 550 | ||
| 551 | 0 | if(self.exhaust) { |
| 552 | 0 | queryOptions |= QueryCommand.OPTS_EXHAUST; |
| 553 | } | |
| 554 | ||
| 555 | // Unpack the read preference to set slave ok correctly | |
| 556 | 0 | var read = self.read instanceof ReadPreference ? self.read.mode : self.read; |
| 557 | ||
| 558 | // if(self.read == 'secondary') | |
| 559 | 0 | if(read == ReadPreference.PRIMARY_PREFERRED |
| 560 | || read == ReadPreference.SECONDARY | |
| 561 | || read == ReadPreference.SECONDARY_PREFERRED | |
| 562 | || read == ReadPreference.NEAREST) { | |
| 563 | 0 | queryOptions |= QueryCommand.OPTS_SLAVE; |
| 564 | } | |
| 565 | ||
| 566 | // Override slaveOk from the user | |
| 567 | 0 | if(self.slaveOk) { |
| 568 | 0 | queryOptions |= QueryCommand.OPTS_SLAVE; |
| 569 | } | |
| 570 | ||
| 571 | 0 | if(self.partial) { |
| 572 | 0 | queryOptions |= QueryCommand.OPTS_PARTIAL; |
| 573 | } | |
| 574 | ||
| 575 | // limitValue of -1 is a special case used by Db#eval | |
| 576 | 0 | var numberToReturn = self.limitValue == -1 ? -1 : limitRequest(self); |
| 577 | ||
| 578 | // Check if we need a special selector | |
| 579 | 0 | if(self.sortValue != null || self.explainValue != null || self.hint != null || self.snapshot != null |
| 580 | || self.returnKey != null || self.maxScan != null || self.min != null || self.max != null | |
| 581 | || self.showDiskLoc != null || self.comment != null || typeof self.maxTimeMSValue == 'number') { | |
| 582 | ||
| 583 | // Build special selector | |
| 584 | 0 | var specialSelector = {'$query':self.selector}; |
| 585 | 0 | if(self.sortValue != null) specialSelector['orderby'] = utils.formattedOrderClause(self.sortValue); |
| 586 | 0 | if(self.hint != null && self.hint.constructor == Object) specialSelector['$hint'] = self.hint; |
| 587 | 0 | if(self.snapshot != null) specialSelector['$snapshot'] = true; |
| 588 | 0 | if(self.returnKey != null) specialSelector['$returnKey'] = self.returnKey; |
| 589 | 0 | if(self.maxScan != null) specialSelector['$maxScan'] = self.maxScan; |
| 590 | 0 | if(self.min != null) specialSelector['$min'] = self.min; |
| 591 | 0 | if(self.max != null) specialSelector['$max'] = self.max; |
| 592 | 0 | if(self.showDiskLoc != null) specialSelector['$showDiskLoc'] = self.showDiskLoc; |
| 593 | 0 | if(self.comment != null) specialSelector['$comment'] = self.comment; |
| 594 | ||
| 595 | // If we are querying the $cmd collection we need to add maxTimeMS as a field | |
| 596 | // otherwise for a normal query it's a "special selector" $maxTimeMS | |
| 597 | 0 | if(typeof self.maxTimeMSValue == 'number' |
| 598 | && self.collectionName.indexOf('.$cmd') != -1) { | |
| 599 | 0 | specialSelector['maxTimeMS'] = self.maxTimeMSValue; |
| 600 | 0 | } else if(typeof self.maxTimeMSValue == 'number' |
| 601 | && self.collectionName.indexOf('.$cmd') == -1) { | |
| 602 | 0 | specialSelector['$maxTimeMS'] = self.maxTimeMSValue; |
| 603 | } | |
| 604 | ||
| 605 | // If we have explain set only return a single document with automatic cursor close | |
| 606 | 0 | if(self.explainValue != null) { |
| 607 | 0 | numberToReturn = (-1)*Math.abs(numberToReturn); |
| 608 | 0 | specialSelector['$explain'] = true; |
| 609 | } | |
| 610 | ||
| 611 | // Return the query | |
| 612 | 0 | return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, specialSelector, self.fields); |
| 613 | } else { | |
| 614 | 0 | return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, self.selector, self.fields); |
| 615 | } | |
| 616 | }; | |
| 617 | ||
| 618 | /** | |
| 619 | * @return {Object} Returns an object containing the sort value of this cursor with | |
| 620 | * the proper formatting that can be used internally in this cursor. | |
| 621 | * @ignore | |
| 622 | * @api private | |
| 623 | */ | |
| 624 | 1 | Cursor.prototype.formattedOrderClause = function() { |
| 625 | 0 | return utils.formattedOrderClause(this.sortValue); |
| 626 | }; | |
| 627 | ||
| 628 | /** | |
| 629 | * Converts the value of the sort direction into its equivalent numerical value. | |
| 630 | * | |
| 631 | * @param sortDirection {String|number} Range of acceptable values: | |
| 632 | * 'ascending', 'descending', 'asc', 'desc', 1, -1 | |
| 633 | * | |
| 634 | * @return {number} The equivalent numerical value | |
| 635 | * @throws Error if the given sortDirection is invalid | |
| 636 | * @ignore | |
| 637 | * @api private | |
| 638 | */ | |
| 639 | 1 | Cursor.prototype.formatSortValue = function(sortDirection) { |
| 640 | 0 | return utils.formatSortValue(sortDirection); |
| 641 | }; | |
| 642 | ||
| 643 | /** | |
| 644 | * Gets the next document from the cursor. | |
| 645 | * | |
| 646 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. | |
| 647 | * @api public | |
| 648 | */ | |
| 649 | 1 | Cursor.prototype.nextObject = function(options, callback) { |
| 650 | 0 | var self = this; |
| 651 | ||
| 652 | 0 | if(typeof options == 'function') { |
| 653 | 0 | callback = options; |
| 654 | 0 | options = {}; |
| 655 | } | |
| 656 | ||
| 657 | 0 | if(self.state == Cursor.INIT) { |
| 658 | 0 | var cmd; |
| 659 | 0 | try { |
| 660 | 0 | cmd = generateQueryCommand(self); |
| 661 | } catch (err) { | |
| 662 | 0 | return callback(err, null); |
| 663 | } | |
| 664 | ||
| 665 | // No need to check the keys | |
| 666 | 0 | var queryOptions = {exhaust: self.exhaust |
| 667 | , raw:self.raw | |
| 668 | , read:self.read | |
| 669 | , connection:self.connection | |
| 670 | , checkKeys: false}; | |
| 671 | ||
| 672 | // Execute command | |
| 673 | 0 | var commandHandler = function(err, result) { |
| 674 | // If on reconnect, the command got given a different connection, switch | |
| 675 | // the whole cursor to it. | |
| 676 | 0 | self.connection = queryOptions.connection; |
| 677 | 0 | self.state = Cursor.OPEN; |
| 678 | 0 | if(err != null && result == null) return callback(utils.toError(err), null); |
| 679 | ||
| 680 | 0 | if(err == null && (result == null || result.documents == null || !Array.isArray(result.documents))) { |
| 681 | 0 | return self.close(function() {callback(new Error("command failed to return results"), null);}); |
| 682 | } | |
| 683 | ||
| 684 | 0 | if(err == null && result && result.documents[0] && result.documents[0]['$err']) { |
| 685 | 0 | return self.close(function() {callback(utils.toError(result.documents[0]['$err']), null);}); |
| 686 | } | |
| 687 | ||
| 688 | 0 | if(err == null && result && result.documents[0] && result.documents[0]['errmsg']) { |
| 689 | 0 | return self.close(function() {callback(utils.toError(result.documents[0]), null);}); |
| 690 | } | |
| 691 | ||
| 692 | 0 | self.queryRun = true; |
| 693 | 0 | self.state = Cursor.OPEN; // Adjust the state of the cursor |
| 694 | 0 | self.cursorId = result.cursorId; |
| 695 | 0 | self.totalNumberOfRecords = result.numberReturned; |
| 696 | ||
| 697 | // Add the new documents to the list of items, using forloop to avoid | |
| 698 | // new array allocations and copying | |
| 699 | 0 | for(var i = 0; i < result.documents.length; i++) { |
| 700 | 0 | self.items.push(result.documents[i]); |
| 701 | } | |
| 702 | ||
| 703 | // If we have noReturn set just return (not modifying the internal item list) | |
| 704 | // used for toArray | |
| 705 | 0 | if(options.noReturn) { |
| 706 | 0 | return callback(null, true); |
| 707 | } | |
| 708 | ||
| 709 | // Ignore callbacks until the cursor is dead for exhausted | |
| 710 | 0 | if(self.exhaust && result.cursorId.toString() == "0") { |
| 711 | 0 | self.nextObject(callback); |
| 712 | 0 | } else if(self.exhaust == false || self.exhaust == null) { |
| 713 | 0 | self.nextObject(callback); |
| 714 | } | |
| 715 | }; | |
| 716 | ||
| 717 | // If we have no connection set on this cursor check one out | |
| 718 | 0 | if(self.connection == null) { |
| 719 | 0 | try { |
| 720 | 0 | self.connection = self.db.serverConfig.checkoutReader(this.read); |
| 721 | // Add to the query options | |
| 722 | 0 | queryOptions.connection = self.connection; |
| 723 | } catch(err) { | |
| 724 | 0 | return callback(utils.toError(err), null); |
| 725 | } | |
| 726 | } | |
| 727 | ||
| 728 | // Execute the command | |
| 729 | 0 | self.db._executeQueryCommand(cmd, queryOptions, commandHandler); |
| 730 | // Set the command handler to null | |
| 731 | 0 | commandHandler = null; |
| 732 | 0 | } else if(self.items.length) { |
| 733 | 0 | callback(null, self.items.shift()); |
| 734 | 0 | } else if(self.cursorId.greaterThan(Long.fromInt(0))) { |
| 735 | 0 | getMore(self, callback); |
| 736 | } else { | |
| 737 | // Force cursor to stay open | |
| 738 | 0 | return self.close(function() {callback(null, null);}); |
| 739 | } | |
| 740 | } | |
| 741 | ||
| 742 | /** | |
| 743 | * Gets more results from the database if any. | |
| 744 | * | |
| 745 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. | |
| 746 | * @ignore | |
| 747 | * @api private | |
| 748 | */ | |
| 749 | 1 | var getMore = function(self, options, callback) { |
| 750 | 0 | var limit = 0; |
| 751 | ||
| 752 | 0 | if(typeof options == 'function') { |
| 753 | 0 | callback = options; |
| 754 | 0 | options = {}; |
| 755 | } | |
| 756 | ||
| 757 | 0 | if(self.state == Cursor.GET_MORE) return callback(null, null); |
| 758 | ||
| 759 | // Set get more in progress | |
| 760 | 0 | self.state = Cursor.GET_MORE; |
| 761 | ||
| 762 | // Set options | |
| 763 | 0 | if (!self.tailable && self.limitValue > 0) { |
| 764 | 0 | limit = self.limitValue - self.totalNumberOfRecords; |
| 765 | 0 | if (limit < 1) { |
| 766 | 0 | self.close(function() {callback(null, null);}); |
| 767 | 0 | return; |
| 768 | } | |
| 769 | } | |
| 770 | ||
| 771 | 0 | try { |
| 772 | 0 | var getMoreCommand = new GetMoreCommand( |
| 773 | self.db | |
| 774 | , self.collectionName | |
| 775 | , limitRequest(self) | |
| 776 | , self.cursorId | |
| 777 | ); | |
| 778 | ||
| 779 | // Set up options | |
| 780 | 0 | var command_options = {read: self.read, raw: self.raw, connection:self.connection }; |
| 781 | ||
| 782 | // Execute the command | |
| 783 | 0 | self.db._executeQueryCommand(getMoreCommand, command_options, function(err, result) { |
| 784 | 0 | var cbValue; |
| 785 | ||
| 786 | // Get more done | |
| 787 | 0 | self.state = Cursor.OPEN; |
| 788 | ||
| 789 | 0 | if(err != null) { |
| 790 | 0 | self.state = Cursor.CLOSED; |
| 791 | 0 | return callback(utils.toError(err), null); |
| 792 | } | |
| 793 | ||
| 794 | // Ensure we get a valid result | |
| 795 | 0 | if(!result || !result.documents) { |
| 796 | 0 | self.state = Cursor.CLOSED; |
| 797 | 0 | return callback(utils.toError("command failed to return results"), null) |
| 798 | } | |
| 799 | ||
| 800 | // If the QueryFailure flag is set | |
| 801 | 0 | if((result.responseFlag & (1 << 1)) != 0) { |
| 802 | 0 | self.state = Cursor.CLOSED; |
| 803 | 0 | return callback(utils.toError("QueryFailure flag set on getmore command"), null); |
| 804 | } | |
| 805 | ||
| 806 | 0 | try { |
| 807 | 0 | var isDead = 1 === result.responseFlag && result.cursorId.isZero(); |
| 808 | ||
| 809 | 0 | self.cursorId = result.cursorId; |
| 810 | 0 | self.totalNumberOfRecords += result.numberReturned; |
| 811 | ||
| 812 | // Determine if there's more documents to fetch | |
| 813 | 0 | if(result.numberReturned > 0) { |
| 814 | 0 | if (self.limitValue > 0) { |
| 815 | 0 | var excessResult = self.totalNumberOfRecords - self.limitValue; |
| 816 | ||
| 817 | 0 | if (excessResult > 0) { |
| 818 | 0 | result.documents.splice(-1 * excessResult, excessResult); |
| 819 | } | |
| 820 | } | |
| 821 | ||
| 822 | // Reset the tries for awaitdata if we are using it | |
| 823 | 0 | self.currentNumberOfRetries = self.numberOfRetries; |
| 824 | // Get the documents | |
| 825 | 0 | for(var i = 0; i < result.documents.length; i++) { |
| 826 | 0 | self.items.push(result.documents[i]); |
| 827 | } | |
| 828 | ||
| 829 | // Don's shift a document out as we need it for toArray | |
| 830 | 0 | if(options.noReturn) { |
| 831 | 0 | cbValue = true; |
| 832 | } else { | |
| 833 | 0 | cbValue = self.items.shift(); |
| 834 | } | |
| 835 | 0 | } else if(self.tailable && !isDead && self.awaitdata) { |
| 836 | // Excute the tailable cursor once more, will timeout after ~4 sec if awaitdata used | |
| 837 | 0 | self.currentNumberOfRetries = self.currentNumberOfRetries - 1; |
| 838 | 0 | if(self.currentNumberOfRetries == 0) { |
| 839 | 0 | self.close(function() { |
| 840 | 0 | callback(new Error("tailable cursor timed out"), null); |
| 841 | }); | |
| 842 | } else { | |
| 843 | 0 | getMore(self, callback); |
| 844 | } | |
| 845 | 0 | } else if(self.tailable && !isDead) { |
| 846 | 0 | self.getMoreTimer = setTimeout(function() { getMore(self, callback); }, self.tailableRetryInterval); |
| 847 | } else { | |
| 848 | 0 | self.close(function() {callback(null, null); }); |
| 849 | } | |
| 850 | ||
| 851 | 0 | result = null; |
| 852 | } catch(err) { | |
| 853 | 0 | callback(utils.toError(err), null); |
| 854 | } | |
| 855 | 0 | if (cbValue != null) callback(null, cbValue); |
| 856 | }); | |
| 857 | ||
| 858 | 0 | getMoreCommand = null; |
| 859 | } catch(err) { | |
| 860 | // Get more done | |
| 861 | 0 | self.state = Cursor.OPEN; |
| 862 | ||
| 863 | 0 | var handleClose = function() { |
| 864 | 0 | callback(utils.toError(err), null); |
| 865 | }; | |
| 866 | ||
| 867 | 0 | self.close(handleClose); |
| 868 | 0 | handleClose = null; |
| 869 | } | |
| 870 | } | |
| 871 | ||
| 872 | /** | |
| 873 | * Gets a detailed information about how the query is performed on this cursor and how | |
| 874 | * long it took the database to process it. | |
| 875 | * | |
| 876 | * @param {Function} callback this will be called after executing this method. The first parameter will always be null while the second parameter will be an object containing the details. | |
| 877 | * @api public | |
| 878 | */ | |
| 879 | 1 | Cursor.prototype.explain = function(callback) { |
| 880 | 0 | var limit = (-1)*Math.abs(this.limitValue); |
| 881 | ||
| 882 | // Create a new cursor and fetch the plan | |
| 883 | 0 | var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, { |
| 884 | skip: this.skipValue | |
| 885 | , limit:limit | |
| 886 | , sort: this.sortValue | |
| 887 | , hint: this.hint | |
| 888 | , explain: true | |
| 889 | , snapshot: this.snapshot | |
| 890 | , timeout: this.timeout | |
| 891 | , tailable: this.tailable | |
| 892 | , batchSize: this.batchSizeValue | |
| 893 | , slaveOk: this.slaveOk | |
| 894 | , raw: this.raw | |
| 895 | , read: this.read | |
| 896 | , returnKey: this.returnKey | |
| 897 | , maxScan: this.maxScan | |
| 898 | , min: this.min | |
| 899 | , max: this.max | |
| 900 | , showDiskLoc: this.showDiskLoc | |
| 901 | , comment: this.comment | |
| 902 | , awaitdata: this.awaitdata | |
| 903 | , oplogReplay: this.oplogReplay | |
| 904 | , numberOfRetries: this.numberOfRetries | |
| 905 | , dbName: this.dbName | |
| 906 | }); | |
| 907 | ||
| 908 | // Fetch the explaination document | |
| 909 | 0 | cursor.nextObject(function(err, item) { |
| 910 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 911 | // close the cursor | |
| 912 | 0 | cursor.close(function(err, result) { |
| 913 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 914 | 0 | callback(null, item); |
| 915 | }); | |
| 916 | }); | |
| 917 | }; | |
| 918 | ||
| 919 | /** | |
| 920 | * Returns a Node ReadStream interface for this cursor. | |
| 921 | * | |
| 922 | * Options | |
| 923 | * - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting. | |
| 924 | * | |
| 925 | * @return {CursorStream} returns a stream object. | |
| 926 | * @api public | |
| 927 | */ | |
| 928 | 1 | Cursor.prototype.stream = function stream(options) { |
| 929 | 0 | return new CursorStream(this, options); |
| 930 | } | |
| 931 | ||
| 932 | /** | |
| 933 | * Close the cursor. | |
| 934 | * | |
| 935 | * @param {Function} callback this will be called after executing this method. The first parameter will always contain null while the second parameter will contain a reference to this cursor. | |
| 936 | * @return {null} | |
| 937 | * @api public | |
| 938 | */ | |
| 939 | 1 | Cursor.prototype.close = function(callback) { |
| 940 | 0 | var self = this |
| 941 | 0 | this.getMoreTimer && clearTimeout(this.getMoreTimer); |
| 942 | // Close the cursor if not needed | |
| 943 | 0 | if(this.cursorId instanceof Long && this.cursorId.greaterThan(Long.fromInt(0))) { |
| 944 | 0 | try { |
| 945 | 0 | var command = new KillCursorCommand(this.db, [this.cursorId]); |
| 946 | // Added an empty callback to ensure we don't throw any null exceptions | |
| 947 | 0 | this.db._executeQueryCommand(command, {read:self.read, raw:self.raw, connection:self.connection}); |
| 948 | } catch(err) {} | |
| 949 | } | |
| 950 | ||
| 951 | // Null out the connection | |
| 952 | 0 | self.connection = null; |
| 953 | // Reset cursor id | |
| 954 | 0 | this.cursorId = Long.fromInt(0); |
| 955 | // Set to closed status | |
| 956 | 0 | this.state = Cursor.CLOSED; |
| 957 | ||
| 958 | 0 | if(callback) { |
| 959 | 0 | callback(null, self); |
| 960 | 0 | self.items = []; |
| 961 | } | |
| 962 | ||
| 963 | 0 | return this; |
| 964 | }; | |
| 965 | ||
| 966 | /** | |
| 967 | * Check if the cursor is closed or open. | |
| 968 | * | |
| 969 | * @return {Boolean} returns the state of the cursor. | |
| 970 | * @api public | |
| 971 | */ | |
| 972 | 1 | Cursor.prototype.isClosed = function() { |
| 973 | 0 | return this.state == Cursor.CLOSED ? true : false; |
| 974 | }; | |
| 975 | ||
| 976 | /** | |
| 977 | * Init state | |
| 978 | * | |
| 979 | * @classconstant INIT | |
| 980 | **/ | |
| 981 | 1 | Cursor.INIT = 0; |
| 982 | ||
| 983 | /** | |
| 984 | * Cursor open | |
| 985 | * | |
| 986 | * @classconstant OPEN | |
| 987 | **/ | |
| 988 | 1 | Cursor.OPEN = 1; |
| 989 | ||
| 990 | /** | |
| 991 | * Cursor closed | |
| 992 | * | |
| 993 | * @classconstant CLOSED | |
| 994 | **/ | |
| 995 | 1 | Cursor.CLOSED = 2; |
| 996 | ||
| 997 | /** | |
| 998 | * Cursor performing a get more | |
| 999 | * | |
| 1000 | * @classconstant OPEN | |
| 1001 | **/ | |
| 1002 | 1 | Cursor.GET_MORE = 3; |
| 1003 | ||
| 1004 | /** | |
| 1005 | * @ignore | |
| 1006 | * @api private | |
| 1007 | */ | |
| 1008 | 1 | exports.Cursor = Cursor; |
| 1009 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var timers = require('timers'); |
| 2 | ||
| 3 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 4 | 1 | var processor = require('./utils').processor(); |
| 5 | ||
| 6 | /** | |
| 7 | * Module dependecies. | |
| 8 | */ | |
| 9 | 1 | var Stream = require('stream').Stream; |
| 10 | ||
| 11 | /** | |
| 12 | * CursorStream | |
| 13 | * | |
| 14 | * Returns a stream interface for the **cursor**. | |
| 15 | * | |
| 16 | * Options | |
| 17 | * - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting. | |
| 18 | * | |
| 19 | * Events | |
| 20 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 21 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 22 | * - **close** {function() {}} the end event triggers when there is no more documents available. | |
| 23 | * | |
| 24 | * @class Represents a CursorStream. | |
| 25 | * @param {Cursor} cursor a cursor object that the stream wraps. | |
| 26 | * @return {Stream} | |
| 27 | */ | |
| 28 | 1 | function CursorStream(cursor, options) { |
| 29 | 0 | if(!(this instanceof CursorStream)) return new CursorStream(cursor); |
| 30 | 0 | options = options ? options : {}; |
| 31 | ||
| 32 | 0 | Stream.call(this); |
| 33 | ||
| 34 | 0 | this.readable = true; |
| 35 | 0 | this.paused = false; |
| 36 | 0 | this._cursor = cursor; |
| 37 | 0 | this._destroyed = null; |
| 38 | 0 | this.options = options; |
| 39 | ||
| 40 | // give time to hook up events | |
| 41 | 0 | var self = this; |
| 42 | 0 | process.nextTick(function() { |
| 43 | 0 | self._init(); |
| 44 | }); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Inherit from Stream | |
| 49 | * @ignore | |
| 50 | * @api private | |
| 51 | */ | |
| 52 | 1 | CursorStream.prototype.__proto__ = Stream.prototype; |
| 53 | ||
| 54 | /** | |
| 55 | * Flag stating whether or not this stream is readable. | |
| 56 | */ | |
| 57 | 1 | CursorStream.prototype.readable; |
| 58 | ||
| 59 | /** | |
| 60 | * Flag stating whether or not this stream is paused. | |
| 61 | */ | |
| 62 | 1 | CursorStream.prototype.paused; |
| 63 | ||
| 64 | /** | |
| 65 | * Initialize the cursor. | |
| 66 | * @ignore | |
| 67 | * @api private | |
| 68 | */ | |
| 69 | 1 | CursorStream.prototype._init = function () { |
| 70 | 0 | if (this._destroyed) return; |
| 71 | 0 | this._next(); |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Pull the next document from the cursor. | |
| 76 | * @ignore | |
| 77 | * @api private | |
| 78 | */ | |
| 79 | 1 | CursorStream.prototype._next = function () { |
| 80 | 0 | if(this.paused || this._destroyed) return; |
| 81 | ||
| 82 | 0 | var self = this; |
| 83 | // Get the next object | |
| 84 | 0 | processor(function() { |
| 85 | 0 | if(self.paused || self._destroyed) return; |
| 86 | ||
| 87 | 0 | self._cursor.nextObject(function (err, doc) { |
| 88 | 0 | self._onNextObject(err, doc); |
| 89 | }); | |
| 90 | }); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Handle each document as its returned from the cursor. | |
| 95 | * @ignore | |
| 96 | * @api private | |
| 97 | */ | |
| 98 | 1 | CursorStream.prototype._onNextObject = function (err, doc) { |
| 99 | 0 | if(err) return this.destroy(err); |
| 100 | ||
| 101 | // when doc is null we hit the end of the cursor | |
| 102 | 0 | if(!doc && (this._cursor.state == 1 || this._cursor.state == 2)) { |
| 103 | 0 | this.emit('end') |
| 104 | 0 | return this.destroy(); |
| 105 | 0 | } else if(doc) { |
| 106 | 0 | var data = typeof this.options.transform == 'function' ? this.options.transform(doc) : doc; |
| 107 | 0 | this.emit('data', data); |
| 108 | 0 | this._next(); |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Pauses the stream. | |
| 114 | * | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | CursorStream.prototype.pause = function () { |
| 118 | 0 | this.paused = true; |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Resumes the stream. | |
| 123 | * | |
| 124 | * @api public | |
| 125 | */ | |
| 126 | 1 | CursorStream.prototype.resume = function () { |
| 127 | 0 | var self = this; |
| 128 | ||
| 129 | // Don't do anything if we are not paused | |
| 130 | 0 | if(!this.paused) return; |
| 131 | 0 | if(!this._cursor.state == 3) return; |
| 132 | ||
| 133 | 0 | process.nextTick(function() { |
| 134 | 0 | self.paused = false; |
| 135 | // Only trigger more fetching if the cursor is open | |
| 136 | 0 | self._next(); |
| 137 | }) | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Destroys the stream, closing the underlying | |
| 142 | * cursor. No more events will be emitted. | |
| 143 | * | |
| 144 | * @api public | |
| 145 | */ | |
| 146 | 1 | CursorStream.prototype.destroy = function (err) { |
| 147 | 0 | if (this._destroyed) return; |
| 148 | 0 | this._destroyed = true; |
| 149 | 0 | this.readable = false; |
| 150 | ||
| 151 | 0 | this._cursor.close(); |
| 152 | ||
| 153 | 0 | if(err) { |
| 154 | 0 | this.emit('error', err); |
| 155 | } | |
| 156 | ||
| 157 | 0 | this.emit('close'); |
| 158 | } | |
| 159 | ||
| 160 | // TODO - maybe implement the raw option to pass binary? | |
| 161 | //CursorStream.prototype.setEncoding = function () { | |
| 162 | //} | |
| 163 | ||
| 164 | 1 | module.exports = exports = CursorStream; |
| 165 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | * @ignore | |
| 4 | */ | |
| 5 | 1 | var QueryCommand = require('./commands/query_command').QueryCommand |
| 6 | , DbCommand = require('./commands/db_command').DbCommand | |
| 7 | , MongoReply = require('./responses/mongo_reply').MongoReply | |
| 8 | , Admin = require('./admin').Admin | |
| 9 | , Collection = require('./collection').Collection | |
| 10 | , Server = require('./connection/server').Server | |
| 11 | , ReplSet = require('./connection/repl_set/repl_set').ReplSet | |
| 12 | , ReadPreference = require('./connection/read_preference').ReadPreference | |
| 13 | , Mongos = require('./connection/mongos').Mongos | |
| 14 | , Cursor = require('./cursor').Cursor | |
| 15 | , EventEmitter = require('events').EventEmitter | |
| 16 | , inherits = require('util').inherits | |
| 17 | , crypto = require('crypto') | |
| 18 | , timers = require('timers') | |
| 19 | , utils = require('./utils') | |
| 20 | ||
| 21 | // Authentication methods | |
| 22 | , mongodb_cr_authenticate = require('./auth/mongodb_cr.js').authenticate | |
| 23 | , mongodb_gssapi_authenticate = require('./auth/mongodb_gssapi.js').authenticate | |
| 24 | , mongodb_sspi_authenticate = require('./auth/mongodb_sspi.js').authenticate | |
| 25 | , mongodb_plain_authenticate = require('./auth/mongodb_plain.js').authenticate | |
| 26 | , mongodb_x509_authenticate = require('./auth/mongodb_x509.js').authenticate; | |
| 27 | ||
| 28 | 1 | var hasKerberos = false; |
| 29 | // Check if we have a the kerberos library | |
| 30 | 1 | try { |
| 31 | 1 | require('kerberos'); |
| 32 | 1 | hasKerberos = true; |
| 33 | } catch(err) {} | |
| 34 | ||
| 35 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 36 | 1 | var processor = require('./utils').processor(); |
| 37 | ||
| 38 | /** | |
| 39 | * Create a new Db instance. | |
| 40 | * | |
| 41 | * Options | |
| 42 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 43 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 44 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 45 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 46 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 47 | * - **native_parser** {Boolean, default:false}, use c++ bson parser. | |
| 48 | * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. | |
| 49 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 50 | * - **serializeFunctions** {Boolean, default:false}, serialize functions. | |
| 51 | * - **raw** {Boolean, default:false}, perform operations using raw bson buffers. | |
| 52 | * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. | |
| 53 | * - **retryMiliSeconds** {Number, default:5000}, number of milliseconds between retries. | |
| 54 | * - **numberOfRetries** {Number, default:5}, number of retries off connection. | |
| 55 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 56 | * - **slaveOk** {Number, default:null}, force setting of SlaveOk flag on queries (only use when explicitly connecting to a secondary server). | |
| 57 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 58 | * - **bufferMaxEntries** {Boolean, default: -1}, sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited | |
| 59 | * | |
| 60 | * Deprecated Options | |
| 61 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 62 | * | |
| 63 | * @class Represents a Db | |
| 64 | * @param {String} databaseName name of the database. | |
| 65 | * @param {Object} serverConfig server config object. | |
| 66 | * @param {Object} [options] additional options for the collection. | |
| 67 | */ | |
| 68 | 1 | function Db(databaseName, serverConfig, options) { |
| 69 | 0 | if(!(this instanceof Db)) return new Db(databaseName, serverConfig, options); |
| 70 | 0 | EventEmitter.call(this); |
| 71 | 0 | var self = this; |
| 72 | 0 | this.databaseName = databaseName; |
| 73 | 0 | this.serverConfig = serverConfig; |
| 74 | 0 | this.options = options == null ? {} : options; |
| 75 | // State to check against if the user force closed db | |
| 76 | 0 | this._applicationClosed = false; |
| 77 | // Fetch the override flag if any | |
| 78 | 0 | var overrideUsedFlag = this.options['override_used_flag'] == null ? false : this.options['override_used_flag']; |
| 79 | ||
| 80 | // Verify that nobody is using this config | |
| 81 | 0 | if(!overrideUsedFlag && this.serverConfig != null && typeof this.serverConfig == 'object' && this.serverConfig._isUsed && this.serverConfig._isUsed()) { |
| 82 | 0 | throw new Error('A Server or ReplSet instance cannot be shared across multiple Db instances'); |
| 83 | 0 | } else if(!overrideUsedFlag && typeof this.serverConfig == 'object'){ |
| 84 | // Set being used | |
| 85 | 0 | this.serverConfig._used = true; |
| 86 | } | |
| 87 | ||
| 88 | // Allow slaveOk override | |
| 89 | 0 | this.slaveOk = this.options['slave_ok'] == null ? false : this.options['slave_ok']; |
| 90 | 0 | this.slaveOk = this.options['slaveOk'] == null ? this.slaveOk : this.options['slaveOk']; |
| 91 | ||
| 92 | // Number of operations to buffer before failure | |
| 93 | 0 | this.bufferMaxEntries = typeof this.options['bufferMaxEntries'] == 'number' ? this.options['bufferMaxEntries'] : -1; |
| 94 | ||
| 95 | // Ensure we have a valid db name | |
| 96 | 0 | validateDatabaseName(databaseName); |
| 97 | ||
| 98 | // Contains all the connections for the db | |
| 99 | 0 | try { |
| 100 | 0 | this.native_parser = this.options.native_parser; |
| 101 | // The bson lib | |
| 102 | 0 | var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : require('bson').BSONPure; |
| 103 | // Fetch the serializer object | |
| 104 | 0 | var BSON = bsonLib.BSON; |
| 105 | ||
| 106 | // Create a new instance | |
| 107 | 0 | this.bson = new BSON([bsonLib.Long, bsonLib.ObjectID, bsonLib.Binary, bsonLib.Code, bsonLib.DBRef, bsonLib.Symbol, bsonLib.Double, bsonLib.Timestamp, bsonLib.MaxKey, bsonLib.MinKey]); |
| 108 | 0 | this.bson.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs; |
| 109 | ||
| 110 | // Backward compatibility to access types | |
| 111 | 0 | this.bson_deserializer = bsonLib; |
| 112 | 0 | this.bson_serializer = bsonLib; |
| 113 | ||
| 114 | // Add any overrides to the serializer and deserializer | |
| 115 | 0 | this.bson_deserializer.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs; |
| 116 | } catch (err) { | |
| 117 | // If we tried to instantiate the native driver | |
| 118 | 0 | var msg = 'Native bson parser not compiled, please compile ' |
| 119 | + 'or avoid using native_parser=true'; | |
| 120 | 0 | throw Error(msg); |
| 121 | } | |
| 122 | ||
| 123 | // Internal state of the server | |
| 124 | 0 | this._state = 'disconnected'; |
| 125 | ||
| 126 | 0 | this.pkFactory = this.options.pk == null ? bsonLib.ObjectID : this.options.pk; |
| 127 | 0 | this.forceServerObjectId = this.options.forceServerObjectId != null ? this.options.forceServerObjectId : false; |
| 128 | ||
| 129 | // Added safe | |
| 130 | 0 | this.safe = this.options.safe == null ? false : this.options.safe; |
| 131 | ||
| 132 | // If we have not specified a "safe mode" we just print a warning to the console | |
| 133 | 0 | if(this.options.safe == null && this.options.w == null && this.options.journal == null && this.options.fsync == null) { |
| 134 | 0 | console.log("========================================================================================"); |
| 135 | 0 | console.log("= Please ensure that you set the default write concern for the database by setting ="); |
| 136 | 0 | console.log("= one of the options ="); |
| 137 | 0 | console.log("= ="); |
| 138 | 0 | console.log("= w: (value of > -1 or the string 'majority'), where < 1 means ="); |
| 139 | 0 | console.log("= no write acknowledgement ="); |
| 140 | 0 | console.log("= journal: true/false, wait for flush to journal before acknowledgement ="); |
| 141 | 0 | console.log("= fsync: true/false, wait for flush to file system before acknowledgement ="); |
| 142 | 0 | console.log("= ="); |
| 143 | 0 | console.log("= For backward compatibility safe is still supported and ="); |
| 144 | 0 | console.log("= allows values of [true | false | {j:true} | {w:n, wtimeout:n} | {fsync:true}] ="); |
| 145 | 0 | console.log("= the default value is false which means the driver receives does not ="); |
| 146 | 0 | console.log("= return the information of the success/error of the insert/update/remove ="); |
| 147 | 0 | console.log("= ="); |
| 148 | 0 | console.log("= ex: new Db(new Server('localhost', 27017), {safe:false}) ="); |
| 149 | 0 | console.log("= ="); |
| 150 | 0 | console.log("= http://www.mongodb.org/display/DOCS/getLastError+Command ="); |
| 151 | 0 | console.log("= ="); |
| 152 | 0 | console.log("= The default of no acknowledgement will change in the very near future ="); |
| 153 | 0 | console.log("= ="); |
| 154 | 0 | console.log("= This message will disappear when the default safe is set on the driver Db ="); |
| 155 | 0 | console.log("========================================================================================"); |
| 156 | } | |
| 157 | ||
| 158 | // Internal states variables | |
| 159 | 0 | this.notReplied ={}; |
| 160 | 0 | this.isInitializing = true; |
| 161 | 0 | this.openCalled = false; |
| 162 | ||
| 163 | // Command queue, keeps a list of incoming commands that need to be executed once the connection is up | |
| 164 | 0 | this.commands = []; |
| 165 | ||
| 166 | // Set up logger | |
| 167 | 0 | this.logger = this.options.logger != null |
| 168 | && (typeof this.options.logger.debug == 'function') | |
| 169 | && (typeof this.options.logger.error == 'function') | |
| 170 | && (typeof this.options.logger.log == 'function') | |
| 171 | ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 172 | ||
| 173 | // Associate the logger with the server config | |
| 174 | 0 | this.serverConfig.logger = this.logger; |
| 175 | 0 | if(this.serverConfig.strategyInstance) this.serverConfig.strategyInstance.logger = this.logger; |
| 176 | 0 | this.tag = new Date().getTime(); |
| 177 | // Just keeps list of events we allow | |
| 178 | 0 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[]}; |
| 179 | ||
| 180 | // Controls serialization options | |
| 181 | 0 | this.serializeFunctions = this.options.serializeFunctions != null ? this.options.serializeFunctions : false; |
| 182 | ||
| 183 | // Raw mode | |
| 184 | 0 | this.raw = this.options.raw != null ? this.options.raw : false; |
| 185 | ||
| 186 | // Record query stats | |
| 187 | 0 | this.recordQueryStats = this.options.recordQueryStats != null ? this.options.recordQueryStats : false; |
| 188 | ||
| 189 | // If we have server stats let's make sure the driver objects have it enabled | |
| 190 | 0 | if(this.recordQueryStats == true) { |
| 191 | 0 | this.serverConfig.enableRecordQueryStats(true); |
| 192 | } | |
| 193 | ||
| 194 | // Retry information | |
| 195 | 0 | this.retryMiliSeconds = this.options.retryMiliSeconds != null ? this.options.retryMiliSeconds : 1000; |
| 196 | 0 | this.numberOfRetries = this.options.numberOfRetries != null ? this.options.numberOfRetries : 60; |
| 197 | ||
| 198 | // Set default read preference if any | |
| 199 | 0 | this.readPreference = this.options.readPreference; |
| 200 | ||
| 201 | // Set read preference on serverConfig if none is set | |
| 202 | // but the db one was | |
| 203 | 0 | if(this.serverConfig.options.readPreference == null |
| 204 | && this.readPreference != null) { | |
| 205 | 0 | this.serverConfig.setReadPreference(this.readPreference); |
| 206 | } | |
| 207 | ||
| 208 | // Ensure we keep a reference to this db | |
| 209 | 0 | this.serverConfig._dbStore.add(this); |
| 210 | }; | |
| 211 | ||
| 212 | /** | |
| 213 | * @ignore | |
| 214 | */ | |
| 215 | 1 | function validateDatabaseName(databaseName) { |
| 216 | 0 | if(typeof databaseName !== 'string') throw new Error("database name must be a string"); |
| 217 | 0 | if(databaseName.length === 0) throw new Error("database name cannot be the empty string"); |
| 218 | 0 | if(databaseName == '$external') return; |
| 219 | ||
| 220 | 0 | var invalidChars = [" ", ".", "$", "/", "\\"]; |
| 221 | 0 | for(var i = 0; i < invalidChars.length; i++) { |
| 222 | 0 | if(databaseName.indexOf(invalidChars[i]) != -1) throw new Error("database names cannot contain the character '" + invalidChars[i] + "'"); |
| 223 | } | |
| 224 | } | |
| 225 | ||
| 226 | /** | |
| 227 | * @ignore | |
| 228 | */ | |
| 229 | 1 | inherits(Db, EventEmitter); |
| 230 | ||
| 231 | /** | |
| 232 | * Initialize the database connection. | |
| 233 | * | |
| 234 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the index information or null if an error occurred. | |
| 235 | * @return {null} | |
| 236 | * @api public | |
| 237 | */ | |
| 238 | 1 | Db.prototype.open = function(callback) { |
| 239 | 0 | var self = this; |
| 240 | ||
| 241 | // Check that the user has not called this twice | |
| 242 | 0 | if(this.openCalled) { |
| 243 | // Close db | |
| 244 | 0 | this.close(); |
| 245 | // Throw error | |
| 246 | 0 | throw new Error("db object already connecting, open cannot be called multiple times"); |
| 247 | } | |
| 248 | ||
| 249 | // If we have a specified read preference | |
| 250 | 0 | if(this.readPreference != null) this.serverConfig.setReadPreference(this.readPreference); |
| 251 | ||
| 252 | // Set that db has been opened | |
| 253 | 0 | this.openCalled = true; |
| 254 | ||
| 255 | // Set the status of the server | |
| 256 | 0 | self._state = 'connecting'; |
| 257 | ||
| 258 | // Set up connections | |
| 259 | 0 | if(self.serverConfig instanceof Server || self.serverConfig instanceof ReplSet || self.serverConfig instanceof Mongos) { |
| 260 | // Ensure we have the original options passed in for the server config | |
| 261 | 0 | var connect_options = {}; |
| 262 | 0 | for(var name in self.serverConfig.options) { |
| 263 | 0 | connect_options[name] = self.serverConfig.options[name] |
| 264 | } | |
| 265 | 0 | connect_options.firstCall = true; |
| 266 | ||
| 267 | // Attempt to connect | |
| 268 | 0 | self.serverConfig.connect(self, connect_options, function(err, result) { |
| 269 | 0 | if(err != null) { |
| 270 | // Close db to reset connection | |
| 271 | 0 | return self.close(function () { |
| 272 | // Return error from connection | |
| 273 | 0 | return callback(err, null); |
| 274 | }); | |
| 275 | } | |
| 276 | // Set the status of the server | |
| 277 | 0 | self._state = 'connected'; |
| 278 | // If we have queued up commands execute a command to trigger replays | |
| 279 | 0 | if(self.commands.length > 0) _execute_queued_command(self); |
| 280 | // Callback | |
| 281 | 0 | process.nextTick(function() { |
| 282 | 0 | try { |
| 283 | 0 | callback(null, self); |
| 284 | } catch(err) { | |
| 285 | 0 | self.close(); |
| 286 | 0 | throw err; |
| 287 | } | |
| 288 | }); | |
| 289 | }); | |
| 290 | } else { | |
| 291 | 0 | try { |
| 292 | 0 | callback(Error("Server parameter must be of type Server, ReplSet or Mongos"), null); |
| 293 | } catch(err) { | |
| 294 | 0 | self.close(); |
| 295 | 0 | throw err; |
| 296 | } | |
| 297 | } | |
| 298 | }; | |
| 299 | ||
| 300 | /** | |
| 301 | * Create a new Db instance sharing the current socket connections. | |
| 302 | * | |
| 303 | * @param {String} dbName the name of the database we want to use. | |
| 304 | * @return {Db} a db instance using the new database. | |
| 305 | * @api public | |
| 306 | */ | |
| 307 | 1 | Db.prototype.db = function(dbName) { |
| 308 | // Copy the options and add out internal override of the not shared flag | |
| 309 | 0 | var options = {}; |
| 310 | 0 | for(var key in this.options) { |
| 311 | 0 | options[key] = this.options[key]; |
| 312 | } | |
| 313 | ||
| 314 | // Add override flag | |
| 315 | 0 | options['override_used_flag'] = true; |
| 316 | // Check if the db already exists and reuse if it's the case | |
| 317 | 0 | var db = this.serverConfig._dbStore.fetch(dbName); |
| 318 | ||
| 319 | // Create a new instance | |
| 320 | 0 | if(!db) { |
| 321 | 0 | db = new Db(dbName, this.serverConfig, options); |
| 322 | } | |
| 323 | ||
| 324 | // Return the db object | |
| 325 | 0 | return db; |
| 326 | }; | |
| 327 | ||
| 328 | /** | |
| 329 | * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. | |
| 330 | * | |
| 331 | * @param {Boolean} [forceClose] connection can never be reused. | |
| 332 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results or null if an error occurred. | |
| 333 | * @return {null} | |
| 334 | * @api public | |
| 335 | */ | |
| 336 | 1 | Db.prototype.close = function(forceClose, callback) { |
| 337 | 0 | var self = this; |
| 338 | // Ensure we force close all connections | |
| 339 | 0 | this._applicationClosed = false; |
| 340 | ||
| 341 | 0 | if(typeof forceClose == 'function') { |
| 342 | 0 | callback = forceClose; |
| 343 | 0 | } else if(typeof forceClose == 'boolean') { |
| 344 | 0 | this._applicationClosed = forceClose; |
| 345 | } | |
| 346 | ||
| 347 | 0 | this.serverConfig.close(function(err, result) { |
| 348 | // You can reuse the db as everything is shut down | |
| 349 | 0 | self.openCalled = false; |
| 350 | // If we have a callback call it | |
| 351 | 0 | if(callback) callback(err, result); |
| 352 | }); | |
| 353 | }; | |
| 354 | ||
| 355 | /** | |
| 356 | * Access the Admin database | |
| 357 | * | |
| 358 | * @param {Function} [callback] returns the results. | |
| 359 | * @return {Admin} the admin db object. | |
| 360 | * @api public | |
| 361 | */ | |
| 362 | 1 | Db.prototype.admin = function(callback) { |
| 363 | 0 | if(callback == null) return new Admin(this); |
| 364 | 0 | callback(null, new Admin(this)); |
| 365 | }; | |
| 366 | ||
| 367 | /** | |
| 368 | * Returns a cursor to all the collection information. | |
| 369 | * | |
| 370 | * @param {String} [collectionName] the collection name we wish to retrieve the information from. | |
| 371 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the options or null if an error occurred. | |
| 372 | * @return {null} | |
| 373 | * @api public | |
| 374 | */ | |
| 375 | 1 | Db.prototype.collectionsInfo = function(collectionName, callback) { |
| 376 | 0 | if(callback == null && typeof collectionName == 'function') { callback = collectionName; collectionName = null; } |
| 377 | // Create selector | |
| 378 | 0 | var selector = {}; |
| 379 | // If we are limiting the access to a specific collection name | |
| 380 | 0 | if(collectionName != null) selector.name = this.databaseName + "." + collectionName; |
| 381 | ||
| 382 | // Return Cursor | |
| 383 | // callback for backward compatibility | |
| 384 | 0 | if(callback) { |
| 385 | 0 | callback(null, new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector)); |
| 386 | } else { | |
| 387 | 0 | return new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector); |
| 388 | } | |
| 389 | }; | |
| 390 | ||
| 391 | /** | |
| 392 | * Get the list of all collection names for the specified db | |
| 393 | * | |
| 394 | * Options | |
| 395 | * - **namesOnly** {String, default:false}, Return only the full collection namespace. | |
| 396 | * | |
| 397 | * @param {String} [collectionName] the collection name we wish to filter by. | |
| 398 | * @param {Object} [options] additional options during update. | |
| 399 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collection names or null if an error occurred. | |
| 400 | * @return {null} | |
| 401 | * @api public | |
| 402 | */ | |
| 403 | 1 | Db.prototype.collectionNames = function(collectionName, options, callback) { |
| 404 | 0 | var self = this; |
| 405 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 406 | 0 | callback = args.pop(); |
| 407 | 0 | collectionName = args.length ? args.shift() : null; |
| 408 | 0 | options = args.length ? args.shift() || {} : {}; |
| 409 | ||
| 410 | // Ensure no breaking behavior | |
| 411 | 0 | if(collectionName != null && typeof collectionName == 'object') { |
| 412 | 0 | options = collectionName; |
| 413 | 0 | collectionName = null; |
| 414 | } | |
| 415 | ||
| 416 | // Let's make our own callback to reuse the existing collections info method | |
| 417 | 0 | self.collectionsInfo(collectionName, function(err, cursor) { |
| 418 | 0 | if(err != null) return callback(err, null); |
| 419 | ||
| 420 | 0 | cursor.toArray(function(err, documents) { |
| 421 | 0 | if(err != null) return callback(err, null); |
| 422 | ||
| 423 | // List of result documents that have been filtered | |
| 424 | 0 | var filtered_documents = documents.filter(function(document) { |
| 425 | 0 | return !(document.name.indexOf(self.databaseName) == -1 || document.name.indexOf('$') != -1); |
| 426 | }); | |
| 427 | ||
| 428 | // If we are returning only the names | |
| 429 | 0 | if(options.namesOnly) { |
| 430 | 0 | filtered_documents = filtered_documents.map(function(document) { return document.name }); |
| 431 | } | |
| 432 | ||
| 433 | // Return filtered items | |
| 434 | 0 | callback(null, filtered_documents); |
| 435 | }); | |
| 436 | }); | |
| 437 | }; | |
| 438 | ||
| 439 | /** | |
| 440 | * Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you can | |
| 441 | * can use it without a callback in the following way. var collection = db.collection('mycollection'); | |
| 442 | * | |
| 443 | * Options | |
| 444 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 445 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 446 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 447 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 448 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 449 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 450 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 451 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 452 | * - **strict**, (Boolean, default:false) returns an error if the collection does not exist | |
| 453 | * | |
| 454 | * Deprecated Options | |
| 455 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 456 | * | |
| 457 | * @param {String} collectionName the collection name we wish to access. | |
| 458 | * @param {Object} [options] returns option results. | |
| 459 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collection or null if an error occurred. | |
| 460 | * @return {null} | |
| 461 | * @api public | |
| 462 | */ | |
| 463 | 1 | Db.prototype.collection = function(collectionName, options, callback) { |
| 464 | 0 | var self = this; |
| 465 | 0 | if(typeof options === "function") { callback = options; options = {}; } |
| 466 | // Execute safe | |
| 467 | ||
| 468 | 0 | if(options && (options.strict)) { |
| 469 | 0 | self.collectionNames(collectionName, function(err, collections) { |
| 470 | 0 | if(err != null) return callback(err, null); |
| 471 | ||
| 472 | 0 | if(collections.length == 0) { |
| 473 | 0 | return callback(new Error("Collection " + collectionName + " does not exist. Currently in safe mode."), null); |
| 474 | } else { | |
| 475 | 0 | try { |
| 476 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 477 | } catch(err) { | |
| 478 | 0 | return callback(err, null); |
| 479 | } | |
| 480 | 0 | return callback(null, collection); |
| 481 | } | |
| 482 | }); | |
| 483 | } else { | |
| 484 | 0 | try { |
| 485 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 486 | } catch(err) { | |
| 487 | 0 | if(callback == null) { |
| 488 | 0 | throw err; |
| 489 | } else { | |
| 490 | 0 | return callback(err, null); |
| 491 | } | |
| 492 | } | |
| 493 | ||
| 494 | // If we have no callback return collection object | |
| 495 | 0 | return callback == null ? collection : callback(null, collection); |
| 496 | } | |
| 497 | }; | |
| 498 | ||
| 499 | /** | |
| 500 | * Fetch all collections for the current db. | |
| 501 | * | |
| 502 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collections or null if an error occurred. | |
| 503 | * @return {null} | |
| 504 | * @api public | |
| 505 | */ | |
| 506 | 1 | Db.prototype.collections = function(callback) { |
| 507 | 0 | var self = this; |
| 508 | // Let's get the collection names | |
| 509 | 0 | self.collectionNames(function(err, documents) { |
| 510 | 0 | if(err != null) return callback(err, null); |
| 511 | 0 | var collections = []; |
| 512 | 0 | documents.forEach(function(document) { |
| 513 | 0 | collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''), self.pkFactory)); |
| 514 | }); | |
| 515 | // Return the collection objects | |
| 516 | 0 | callback(null, collections); |
| 517 | }); | |
| 518 | }; | |
| 519 | ||
| 520 | /** | |
| 521 | * Evaluate javascript on the server | |
| 522 | * | |
| 523 | * Options | |
| 524 | * - **nolock** {Boolean, default:false}, Tell MongoDB not to block on the evaulation of the javascript. | |
| 525 | * | |
| 526 | * @param {Code} code javascript to execute on server. | |
| 527 | * @param {Object|Array} [parameters] the parameters for the call. | |
| 528 | * @param {Object} [options] the options | |
| 529 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from eval or null if an error occurred. | |
| 530 | * @return {null} | |
| 531 | * @api public | |
| 532 | */ | |
| 533 | 1 | Db.prototype.eval = function(code, parameters, options, callback) { |
| 534 | // Unpack calls | |
| 535 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 536 | 0 | callback = args.pop(); |
| 537 | 0 | parameters = args.length ? args.shift() : parameters; |
| 538 | 0 | options = args.length ? args.shift() || {} : {}; |
| 539 | ||
| 540 | 0 | var finalCode = code; |
| 541 | 0 | var finalParameters = []; |
| 542 | // If not a code object translate to one | |
| 543 | 0 | if(!(finalCode instanceof this.bsonLib.Code)) { |
| 544 | 0 | finalCode = new this.bsonLib.Code(finalCode); |
| 545 | } | |
| 546 | ||
| 547 | // Ensure the parameters are correct | |
| 548 | 0 | if(parameters != null && parameters.constructor != Array && typeof parameters !== 'function') { |
| 549 | 0 | finalParameters = [parameters]; |
| 550 | 0 | } else if(parameters != null && parameters.constructor == Array && typeof parameters !== 'function') { |
| 551 | 0 | finalParameters = parameters; |
| 552 | } | |
| 553 | ||
| 554 | // Create execution selector | |
| 555 | 0 | var selector = {'$eval':finalCode, 'args':finalParameters}; |
| 556 | // Check if the nolock parameter is passed in | |
| 557 | 0 | if(options['nolock']) { |
| 558 | 0 | selector['nolock'] = options['nolock']; |
| 559 | } | |
| 560 | ||
| 561 | // Set primary read preference | |
| 562 | 0 | options.readPreference = ReadPreference.PRIMARY; |
| 563 | ||
| 564 | // Execute the eval | |
| 565 | 0 | this.collection(DbCommand.SYSTEM_COMMAND_COLLECTION).findOne(selector, options, function(err, result) { |
| 566 | 0 | if(err) return callback(err); |
| 567 | ||
| 568 | 0 | if(result && result.ok == 1) { |
| 569 | 0 | callback(null, result.retval); |
| 570 | 0 | } else if(result) { |
| 571 | 0 | callback(new Error("eval failed: " + result.errmsg), null); return; |
| 572 | } else { | |
| 573 | 0 | callback(err, result); |
| 574 | } | |
| 575 | }); | |
| 576 | }; | |
| 577 | ||
| 578 | /** | |
| 579 | * Dereference a dbref, against a db | |
| 580 | * | |
| 581 | * @param {DBRef} dbRef db reference object we wish to resolve. | |
| 582 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dereference or null if an error occurred. | |
| 583 | * @return {null} | |
| 584 | * @api public | |
| 585 | */ | |
| 586 | 1 | Db.prototype.dereference = function(dbRef, callback) { |
| 587 | 0 | var db = this; |
| 588 | // If we have a db reference then let's get the db first | |
| 589 | 0 | if(dbRef.db != null) db = this.db(dbRef.db); |
| 590 | // Fetch the collection and find the reference | |
| 591 | 0 | var collection = db.collection(dbRef.namespace); |
| 592 | 0 | collection.findOne({'_id':dbRef.oid}, function(err, result) { |
| 593 | 0 | callback(err, result); |
| 594 | }); | |
| 595 | }; | |
| 596 | ||
| 597 | /** | |
| 598 | * Logout user from server, fire off on all connections and remove all auth info | |
| 599 | * | |
| 600 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from logout or null if an error occurred. | |
| 601 | * @return {null} | |
| 602 | * @api public | |
| 603 | */ | |
| 604 | 1 | Db.prototype.logout = function(options, callback) { |
| 605 | 0 | var self = this; |
| 606 | // Unpack calls | |
| 607 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 608 | 0 | callback = args.pop(); |
| 609 | 0 | options = args.length ? args.shift() || {} : {}; |
| 610 | ||
| 611 | // Number of connections we need to logout from | |
| 612 | 0 | var numberOfConnections = this.serverConfig.allRawConnections().length; |
| 613 | ||
| 614 | // Let's generate the logout command object | |
| 615 | 0 | var logoutCommand = DbCommand.logoutCommand(self, {logout:1}, options); |
| 616 | 0 | self._executeQueryCommand(logoutCommand, {onAll:true}, function(err, result) { |
| 617 | // Count down | |
| 618 | 0 | numberOfConnections = numberOfConnections - 1; |
| 619 | // Work around the case where the number of connections are 0 | |
| 620 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 621 | 0 | var internalCallback = callback; |
| 622 | 0 | callback = null; |
| 623 | ||
| 624 | // Remove the db from auths | |
| 625 | 0 | self.serverConfig.auth.remove(self.databaseName); |
| 626 | ||
| 627 | // Handle error result | |
| 628 | 0 | utils.handleSingleCommandResultReturn(true, false, internalCallback)(err, result); |
| 629 | } | |
| 630 | }); | |
| 631 | }; | |
| 632 | ||
| 633 | /** | |
| 634 | * Authenticate a user against the server. | |
| 635 | * authMechanism | |
| 636 | * Options | |
| 637 | * - **authMechanism** {String, default:MONGODB-CR}, The authentication mechanism to use, GSSAPI or MONGODB-CR | |
| 638 | * | |
| 639 | * @param {String} username username. | |
| 640 | * @param {String} password password. | |
| 641 | * @param {Object} [options] the options | |
| 642 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from authentication or null if an error occurred. | |
| 643 | * @return {null} | |
| 644 | * @api public | |
| 645 | */ | |
| 646 | 1 | Db.prototype.authenticate = function(username, password, options, callback) { |
| 647 | 0 | var self = this; |
| 648 | ||
| 649 | 0 | if(typeof options == 'function') { |
| 650 | 0 | callback = options; |
| 651 | 0 | options = {}; |
| 652 | } | |
| 653 | ||
| 654 | // Set default mechanism | |
| 655 | 0 | if(!options.authMechanism) { |
| 656 | 0 | options.authMechanism = 'MONGODB-CR'; |
| 657 | 0 | } else if(options.authMechanism != 'GSSAPI' |
| 658 | && options.authMechanism != 'MONGODB-CR' | |
| 659 | && options.authMechanism != 'MONGODB-X509' | |
| 660 | && options.authMechanism != 'PLAIN') { | |
| 661 | 0 | return callback(new Error("only GSSAPI, PLAIN, MONGODB-X509 or MONGODB-CR is supported by authMechanism")); |
| 662 | } | |
| 663 | ||
| 664 | // the default db to authenticate against is 'this' | |
| 665 | // if authententicate is called from a retry context, it may be another one, like admin | |
| 666 | 0 | var authdb = options.authdb ? options.authdb : self.databaseName; |
| 667 | 0 | authdb = options.authSource ? options.authSource : authdb; |
| 668 | ||
| 669 | // Callback | |
| 670 | 0 | var _callback = function(err, result) { |
| 671 | 0 | if(self.listeners("authenticated").length > 9) { |
| 672 | 0 | self.emit("authenticated", err, result); |
| 673 | } | |
| 674 | ||
| 675 | // Return to caller | |
| 676 | 0 | callback(err, result); |
| 677 | } | |
| 678 | ||
| 679 | // If classic auth delegate to auth command | |
| 680 | 0 | if(options.authMechanism == 'MONGODB-CR') { |
| 681 | 0 | mongodb_cr_authenticate(self, username, password, authdb, options, _callback); |
| 682 | 0 | } else if(options.authMechanism == 'PLAIN') { |
| 683 | 0 | mongodb_plain_authenticate(self, username, password, options, _callback); |
| 684 | 0 | } else if(options.authMechanism == 'MONGODB-X509') { |
| 685 | 0 | mongodb_x509_authenticate(self, username, password, options, _callback); |
| 686 | 0 | } else if(options.authMechanism == 'GSSAPI') { |
| 687 | // | |
| 688 | // Kerberos library is not installed, throw and error | |
| 689 | 0 | if(hasKerberos == false) { |
| 690 | 0 | console.log("========================================================================================"); |
| 691 | 0 | console.log("= Please make sure that you install the Kerberos library to use GSSAPI ="); |
| 692 | 0 | console.log("= ="); |
| 693 | 0 | console.log("= npm install -g kerberos ="); |
| 694 | 0 | console.log("= ="); |
| 695 | 0 | console.log("= The Kerberos package is not installed by default for simplicities sake ="); |
| 696 | 0 | console.log("= and needs to be global install ="); |
| 697 | 0 | console.log("========================================================================================"); |
| 698 | 0 | throw new Error("Kerberos library not installed"); |
| 699 | } | |
| 700 | ||
| 701 | 0 | if(process.platform == 'win32') { |
| 702 | 0 | mongodb_sspi_authenticate(self, username, password, authdb, options, _callback); |
| 703 | } else { | |
| 704 | // We have the kerberos library, execute auth process | |
| 705 | 0 | mongodb_gssapi_authenticate(self, username, password, authdb, options, _callback); |
| 706 | } | |
| 707 | } | |
| 708 | }; | |
| 709 | ||
| 710 | /** | |
| 711 | * Add a user to the database. | |
| 712 | * | |
| 713 | * Options | |
| 714 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 715 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 716 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 717 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 718 | * - **customData**, (Object, default:{}) custom data associated with the user (only Mongodb 2.6 or higher) | |
| 719 | * - **roles**, (Array, default:[]) roles associated with the created user (only Mongodb 2.6 or higher) | |
| 720 | * | |
| 721 | * Deprecated Options | |
| 722 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 723 | * | |
| 724 | * @param {String} username username. | |
| 725 | * @param {String} password password. | |
| 726 | * @param {Object} [options] additional options during update. | |
| 727 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from addUser or null if an error occurred. | |
| 728 | * @return {null} | |
| 729 | * @api public | |
| 730 | */ | |
| 731 | 1 | Db.prototype.addUser = function(username, password, options, callback) { |
| 732 | // Checkout a write connection to get the server capabilities | |
| 733 | 0 | var connection = this.serverConfig.checkoutWriter(); |
| 734 | 0 | if(connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasAuthCommands) { |
| 735 | 0 | return _executeAuthCreateUserCommand(this, username, password, options, callback); |
| 736 | } | |
| 737 | ||
| 738 | // Unpack the parameters | |
| 739 | 0 | var self = this; |
| 740 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 741 | 0 | callback = args.pop(); |
| 742 | 0 | options = args.length ? args.shift() || {} : {}; |
| 743 | ||
| 744 | // Get the error options | |
| 745 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 746 | 0 | errorOptions.w = errorOptions.w == null ? 1 : errorOptions.w; |
| 747 | // Use node md5 generator | |
| 748 | 0 | var md5 = crypto.createHash('md5'); |
| 749 | // Generate keys used for authentication | |
| 750 | 0 | md5.update(username + ":mongo:" + password); |
| 751 | 0 | var userPassword = md5.digest('hex'); |
| 752 | // Fetch a user collection | |
| 753 | 0 | var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION); |
| 754 | // Check if we are inserting the first user | |
| 755 | 0 | collection.count({}, function(err, count) { |
| 756 | // We got an error (f.ex not authorized) | |
| 757 | 0 | if(err != null) return callback(err, null); |
| 758 | // Check if the user exists and update i | |
| 759 | 0 | collection.find({user: username}, {dbName: options['dbName']}).toArray(function(err, documents) { |
| 760 | // We got an error (f.ex not authorized) | |
| 761 | 0 | if(err != null) return callback(err, null); |
| 762 | // Add command keys | |
| 763 | 0 | var commandOptions = errorOptions; |
| 764 | 0 | commandOptions.dbName = options['dbName']; |
| 765 | 0 | commandOptions.upsert = true; |
| 766 | ||
| 767 | // We have a user, let's update the password or upsert if not | |
| 768 | 0 | collection.update({user: username},{$set: {user: username, pwd: userPassword}}, commandOptions, function(err, results, full) { |
| 769 | 0 | if(count == 0 && err) { |
| 770 | 0 | callback(null, [{user:username, pwd:userPassword}]); |
| 771 | 0 | } else if(err) { |
| 772 | 0 | callback(err, null) |
| 773 | } else { | |
| 774 | 0 | callback(null, [{user:username, pwd:userPassword}]); |
| 775 | } | |
| 776 | }); | |
| 777 | }); | |
| 778 | }); | |
| 779 | }; | |
| 780 | ||
| 781 | /** | |
| 782 | * @ignore | |
| 783 | */ | |
| 784 | 1 | var _executeAuthCreateUserCommand = function(self, username, password, options, callback) { |
| 785 | // Special case where there is no password ($external users) | |
| 786 | 0 | if(typeof username == 'string' |
| 787 | && password != null && typeof password == 'object') { | |
| 788 | 0 | callback = options; |
| 789 | 0 | options = password; |
| 790 | 0 | password = null; |
| 791 | } | |
| 792 | ||
| 793 | // Unpack all options | |
| 794 | 0 | if(typeof options == 'function') { |
| 795 | 0 | callback = options; |
| 796 | 0 | options = {}; |
| 797 | } | |
| 798 | ||
| 799 | // Error out if we digestPassword set | |
| 800 | 0 | if(options.digestPassword != null) { |
| 801 | 0 | throw utils.toError("The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option."); |
| 802 | } | |
| 803 | ||
| 804 | // Get additional values | |
| 805 | 0 | var customData = options.customData != null ? options.customData : {}; |
| 806 | 0 | var roles = Array.isArray(options.roles) ? options.roles : []; |
| 807 | 0 | var maxTimeMS = typeof options.maxTimeMS == 'number' ? options.maxTimeMS : null; |
| 808 | ||
| 809 | // If not roles defined print deprecated message | |
| 810 | 0 | if(roles.length == 0) { |
| 811 | 0 | console.log("Creating a user without roles is deprecated in MongoDB >= 2.6"); |
| 812 | } | |
| 813 | ||
| 814 | // Get the error options | |
| 815 | 0 | var writeConcern = _getWriteConcern(self, options); |
| 816 | 0 | var commandOptions = {writeCommand:true}; |
| 817 | 0 | if(options['dbName']) commandOptions.dbName = options['dbName']; |
| 818 | ||
| 819 | // Add maxTimeMS to options if set | |
| 820 | 0 | if(maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; |
| 821 | ||
| 822 | // Check the db name and add roles if needed | |
| 823 | 0 | if((self.databaseName.toLowerCase() == 'admin' || options.dbName == 'admin') && !Array.isArray(options.roles)) { |
| 824 | 0 | roles = ['root'] |
| 825 | 0 | } else if(!Array.isArray(options.roles)) { |
| 826 | 0 | roles = ['dbOwner'] |
| 827 | } | |
| 828 | ||
| 829 | // Build the command to execute | |
| 830 | 0 | var command = { |
| 831 | createUser: username | |
| 832 | , customData: customData | |
| 833 | , roles: roles | |
| 834 | , digestPassword:false | |
| 835 | , writeConcern: writeConcern | |
| 836 | } | |
| 837 | ||
| 838 | // Use node md5 generator | |
| 839 | 0 | var md5 = crypto.createHash('md5'); |
| 840 | // Generate keys used for authentication | |
| 841 | 0 | md5.update(username + ":mongo:" + password); |
| 842 | 0 | var userPassword = md5.digest('hex'); |
| 843 | ||
| 844 | // No password | |
| 845 | 0 | if(typeof password == 'string') { |
| 846 | 0 | command.pwd = userPassword; |
| 847 | } | |
| 848 | ||
| 849 | // console.log("================================== add user") | |
| 850 | // console.dir(command) | |
| 851 | ||
| 852 | // Execute the command | |
| 853 | 0 | self.command(command, commandOptions, function(err, result) { |
| 854 | 0 | if(err) return callback(err, null); |
| 855 | 0 | callback(!result.ok ? utils.toError("Failed to add user " + username) : null |
| 856 | , result.ok ? [{user: username, pwd: ''}] : null); | |
| 857 | }) | |
| 858 | } | |
| 859 | ||
| 860 | /** | |
| 861 | * Remove a user from a database | |
| 862 | * | |
| 863 | * Options | |
| 864 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 865 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 866 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 867 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 868 | * | |
| 869 | * Deprecated Options | |
| 870 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 871 | * | |
| 872 | * @param {String} username username. | |
| 873 | * @param {Object} [options] additional options during update. | |
| 874 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occurred. | |
| 875 | * @return {null} | |
| 876 | * @api public | |
| 877 | */ | |
| 878 | 1 | Db.prototype.removeUser = function(username, options, callback) { |
| 879 | // Checkout a write connection to get the server capabilities | |
| 880 | 0 | var connection = this.serverConfig.checkoutWriter(); |
| 881 | 0 | if(connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasAuthCommands) { |
| 882 | 0 | return _executeAuthRemoveUserCommand(this, username, options, callback); |
| 883 | } | |
| 884 | ||
| 885 | // Unpack the parameters | |
| 886 | 0 | var self = this; |
| 887 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 888 | 0 | callback = args.pop(); |
| 889 | 0 | options = args.length ? args.shift() || {} : {}; |
| 890 | ||
| 891 | // Figure out the safe mode settings | |
| 892 | 0 | var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; |
| 893 | // Override with options passed in if applicable | |
| 894 | 0 | safe = options != null && options['safe'] != null ? options['safe'] : safe; |
| 895 | // Ensure it's at least set to safe | |
| 896 | 0 | safe = safe == null ? {w: 1} : safe; |
| 897 | ||
| 898 | // Fetch a user collection | |
| 899 | 0 | var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION); |
| 900 | 0 | collection.findOne({user: username}, {dbName: options['dbName']}, function(err, user) { |
| 901 | 0 | if(user != null) { |
| 902 | // Add command keys | |
| 903 | 0 | var commandOptions = safe; |
| 904 | 0 | commandOptions.dbName = options['dbName']; |
| 905 | ||
| 906 | 0 | collection.remove({user: username}, commandOptions, function(err, result) { |
| 907 | 0 | callback(err, true); |
| 908 | }); | |
| 909 | } else { | |
| 910 | 0 | callback(err, false); |
| 911 | } | |
| 912 | }); | |
| 913 | }; | |
| 914 | ||
| 915 | 1 | var _executeAuthRemoveUserCommand = function(self, username, options, callback) { |
| 916 | // Unpack all options | |
| 917 | 0 | if(typeof options == 'function') { |
| 918 | 0 | callback = options; |
| 919 | 0 | options = {}; |
| 920 | } | |
| 921 | ||
| 922 | // Get the error options | |
| 923 | 0 | var writeConcern = _getWriteConcern(self, options); |
| 924 | 0 | var commandOptions = {writeCommand:true}; |
| 925 | 0 | if(options['dbName']) commandOptions.dbName = options['dbName']; |
| 926 | ||
| 927 | // Get additional values | |
| 928 | 0 | var maxTimeMS = typeof options.maxTimeMS == 'number' ? options.maxTimeMS : null; |
| 929 | ||
| 930 | // Add maxTimeMS to options if set | |
| 931 | 0 | if(maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; |
| 932 | ||
| 933 | // Build the command to execute | |
| 934 | 0 | var command = { |
| 935 | dropUser: username | |
| 936 | , writeConcern: writeConcern | |
| 937 | } | |
| 938 | ||
| 939 | // Execute the command | |
| 940 | 0 | self.command(command, commandOptions, function(err, result) { |
| 941 | 0 | if(err) return callback(err, null); |
| 942 | 0 | callback(null, result.ok ? true : false); |
| 943 | }) | |
| 944 | } | |
| 945 | ||
| 946 | /** | |
| 947 | * Creates a collection on a server pre-allocating space, need to create f.ex capped collections. | |
| 948 | * | |
| 949 | * Options | |
| 950 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 951 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 952 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 953 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 954 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 955 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 956 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 957 | * - **capped** {Boolean, default:false}, create a capped collection. | |
| 958 | * - **size** {Number}, the size of the capped collection in bytes. | |
| 959 | * - **max** {Number}, the maximum number of documents in the capped collection. | |
| 960 | * - **autoIndexId** {Boolean, default:true}, create an index on the _id field of the document, True by default on MongoDB 2.2 or higher off for version < 2.2. | |
| 961 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 962 | * - **strict**, (Boolean, default:false) throws an error if collection already exists | |
| 963 | * | |
| 964 | * Deprecated Options | |
| 965 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 966 | * | |
| 967 | * @param {String} collectionName the collection name we wish to access. | |
| 968 | * @param {Object} [options] returns option results. | |
| 969 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from createCollection or null if an error occurred. | |
| 970 | * @return {null} | |
| 971 | * @api public | |
| 972 | */ | |
| 973 | 1 | Db.prototype.createCollection = function(collectionName, options, callback) { |
| 974 | 0 | var self = this; |
| 975 | 0 | if(typeof options == 'function') { |
| 976 | 0 | callback = options; |
| 977 | 0 | options = {}; |
| 978 | } | |
| 979 | ||
| 980 | // Figure out the safe mode settings | |
| 981 | 0 | var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; |
| 982 | // Override with options passed in if applicable | |
| 983 | 0 | safe = options != null && options['safe'] != null ? options['safe'] : safe; |
| 984 | // Ensure it's at least set to safe | |
| 985 | 0 | safe = safe == null ? {w: 1} : safe; |
| 986 | ||
| 987 | // Check if we have the name | |
| 988 | 0 | this.collectionNames(collectionName, function(err, collections) { |
| 989 | 0 | if(err != null) return callback(err, null); |
| 990 | ||
| 991 | 0 | var found = false; |
| 992 | 0 | collections.forEach(function(collection) { |
| 993 | 0 | if(collection.name == self.databaseName + "." + collectionName) found = true; |
| 994 | }); | |
| 995 | ||
| 996 | // If the collection exists either throw an exception (if db in safe mode) or return the existing collection | |
| 997 | 0 | if(found && options && options.strict) { |
| 998 | 0 | return callback(new Error("Collection " + collectionName + " already exists. Currently in safe mode."), null); |
| 999 | 0 | } else if(found){ |
| 1000 | 0 | try { |
| 1001 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 1002 | } catch(err) { | |
| 1003 | 0 | return callback(err, null); |
| 1004 | } | |
| 1005 | 0 | return callback(null, collection); |
| 1006 | } | |
| 1007 | ||
| 1008 | // Create a new collection and return it | |
| 1009 | 0 | self._executeQueryCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options) |
| 1010 | , {read:false, safe:safe} | |
| 1011 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 1012 | 0 | if(err) return callback(err, null); |
| 1013 | // Create collection and return | |
| 1014 | 0 | try { |
| 1015 | 0 | return callback(null, new Collection(self, collectionName, self.pkFactory, options)); |
| 1016 | } catch(err) { | |
| 1017 | 0 | return callback(err, null); |
| 1018 | } | |
| 1019 | })); | |
| 1020 | }); | |
| 1021 | }; | |
| 1022 | ||
| 1023 | 1 | var _getReadConcern = function(self, options) { |
| 1024 | 0 | if(options.readPreference) return options.readPreference; |
| 1025 | 0 | if(self.readPreference) return self.readPreference; |
| 1026 | 0 | return 'primary'; |
| 1027 | } | |
| 1028 | ||
| 1029 | /** | |
| 1030 | * Execute a command hash against MongoDB. This lets you acess any commands not available through the api on the server. | |
| 1031 | * | |
| 1032 | * Options | |
| 1033 | * - **maxTimeMS** {Number}, number of miliseconds to wait before aborting the query. | |
| 1034 | * - **ignoreCommandFilter** {Boolean}, overrides the default redirection of certain commands to primary. | |
| 1035 | * - **writeCommand** {Boolean, default: false}, signals this is a write command and to ignore read preferences | |
| 1036 | * - **checkKeys** {Boolean, default: false}, overrides the default not to check the key names for the command | |
| 1037 | * | |
| 1038 | * @param {Object} selector the command hash to send to the server, ex: {ping:1}. | |
| 1039 | * @param {Object} [options] additional options for the command. | |
| 1040 | * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. | |
| 1041 | * @return {null} | |
| 1042 | * @api public | |
| 1043 | */ | |
| 1044 | 1 | Db.prototype.command = function(selector, options, callback) { |
| 1045 | 0 | if(typeof options == 'function') { |
| 1046 | 0 | callback = options; |
| 1047 | 0 | options = {}; |
| 1048 | } | |
| 1049 | ||
| 1050 | // Ignore command preference (I know what I'm doing) | |
| 1051 | 0 | var ignoreCommandFilter = options.ignoreCommandFilter ? options.ignoreCommandFilter : false; |
| 1052 | // Set read preference if we set one | |
| 1053 | 0 | var readPreference = _getReadConcern(this, options); |
| 1054 | ||
| 1055 | // Ensure only commands who support read Prefrences are exeuted otherwise override and use Primary | |
| 1056 | 0 | if(readPreference != false && ignoreCommandFilter == false) { |
| 1057 | 0 | if(selector['group'] || selector['aggregate'] || selector['collStats'] || selector['dbStats'] |
| 1058 | || selector['count'] || selector['distinct'] || selector['geoNear'] || selector['geoSearch'] | |
| 1059 | || selector['geoWalk'] || selector['text'] | |
| 1060 | || (selector['mapreduce'] && (selector.out == 'inline' || selector.out.inline))) { | |
| 1061 | // Set the read preference | |
| 1062 | 0 | options.readPreference = readPreference; |
| 1063 | } else { | |
| 1064 | 0 | options.readPreference = ReadPreference.PRIMARY; |
| 1065 | } | |
| 1066 | 0 | } else if(readPreference != false) { |
| 1067 | 0 | options.readPreference = readPreference; |
| 1068 | } | |
| 1069 | ||
| 1070 | // Add the maxTimeMS option to the command if specified | |
| 1071 | 0 | if(typeof options.maxTimeMS == 'number') { |
| 1072 | 0 | selector.maxTimeMS = options.maxTimeMS |
| 1073 | } | |
| 1074 | ||
| 1075 | // Command options | |
| 1076 | 0 | var command_options = {}; |
| 1077 | ||
| 1078 | // Do we have an override for checkKeys | |
| 1079 | 0 | if(typeof options['checkKeys'] == 'boolean') command_options['checkKeys'] = options['checkKeys']; |
| 1080 | 0 | command_options['checkKeys'] = typeof options['checkKeys'] == 'boolean' ? options['checkKeys'] : false; |
| 1081 | 0 | if(typeof options['serializeFunctions'] == 'boolean') command_options['serializeFunctions'] = options['serializeFunctions']; |
| 1082 | 0 | if(options['dbName']) command_options['dbName'] = options['dbName']; |
| 1083 | ||
| 1084 | // If we have a write command, remove readPreference as an option | |
| 1085 | 0 | if((options.writeCommand |
| 1086 | || selector['findAndModify'] | |
| 1087 | || selector['insert'] || selector['update'] || selector['delete'] | |
| 1088 | || selector['createUser'] || selector['updateUser'] || selector['removeUser']) | |
| 1089 | && options.readPreference) { | |
| 1090 | 0 | delete options['readPreference']; |
| 1091 | } | |
| 1092 | ||
| 1093 | // Execute a query command | |
| 1094 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, selector, command_options), options, function(err, results) { |
| 1095 | 0 | if(err) return callback(err, null); |
| 1096 | 0 | if(results.documents[0].errmsg) |
| 1097 | 0 | return callback(utils.toError(results.documents[0]), null); |
| 1098 | 0 | callback(null, results.documents[0]); |
| 1099 | }); | |
| 1100 | }; | |
| 1101 | ||
| 1102 | /** | |
| 1103 | * Drop a collection from the database, removing it permanently. New accesses will create a new collection. | |
| 1104 | * | |
| 1105 | * @param {String} collectionName the name of the collection we wish to drop. | |
| 1106 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropCollection or null if an error occurred. | |
| 1107 | * @return {null} | |
| 1108 | * @api public | |
| 1109 | */ | |
| 1110 | 1 | Db.prototype.dropCollection = function(collectionName, callback) { |
| 1111 | 0 | var self = this; |
| 1112 | 0 | callback || (callback = function(){}); |
| 1113 | ||
| 1114 | // Drop the collection | |
| 1115 | 0 | this._executeQueryCommand(DbCommand.createDropCollectionCommand(this, collectionName) |
| 1116 | , utils.handleSingleCommandResultReturn(true, false, callback) | |
| 1117 | ); | |
| 1118 | }; | |
| 1119 | ||
| 1120 | /** | |
| 1121 | * Rename a collection. | |
| 1122 | * | |
| 1123 | * Options | |
| 1124 | * - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists. | |
| 1125 | * | |
| 1126 | * @param {String} fromCollection the name of the current collection we wish to rename. | |
| 1127 | * @param {String} toCollection the new name of the collection. | |
| 1128 | * @param {Object} [options] returns option results. | |
| 1129 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from renameCollection or null if an error occurred. | |
| 1130 | * @return {null} | |
| 1131 | * @api public | |
| 1132 | */ | |
| 1133 | 1 | Db.prototype.renameCollection = function(fromCollection, toCollection, options, callback) { |
| 1134 | 0 | var self = this; |
| 1135 | ||
| 1136 | 0 | if(typeof options == 'function') { |
| 1137 | 0 | callback = options; |
| 1138 | 0 | options = {} |
| 1139 | } | |
| 1140 | ||
| 1141 | // Add return new collection | |
| 1142 | 0 | options.new_collection = true; |
| 1143 | ||
| 1144 | // Execute using the collection method | |
| 1145 | 0 | this.collection(fromCollection).rename(toCollection, options, callback); |
| 1146 | }; | |
| 1147 | ||
| 1148 | /** | |
| 1149 | * Return last error message for the given connection, note options can be combined. | |
| 1150 | * | |
| 1151 | * Options | |
| 1152 | * - **fsync** {Boolean, default:false}, option forces the database to fsync all files before returning. | |
| 1153 | * - **j** {Boolean, default:false}, awaits the journal commit before returning, > MongoDB 2.0. | |
| 1154 | * - **w** {Number}, until a write operation has been replicated to N servers. | |
| 1155 | * - **wtimeout** {Number}, number of miliseconds to wait before timing out. | |
| 1156 | * | |
| 1157 | * Connection Options | |
| 1158 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1159 | * | |
| 1160 | * @param {Object} [options] returns option results. | |
| 1161 | * @param {Object} [connectionOptions] returns option results. | |
| 1162 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from lastError or null if an error occurred. | |
| 1163 | * @return {null} | |
| 1164 | * @api public | |
| 1165 | */ | |
| 1166 | 1 | Db.prototype.lastError = function(options, connectionOptions, callback) { |
| 1167 | // Unpack calls | |
| 1168 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1169 | 0 | callback = args.pop(); |
| 1170 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1171 | 0 | connectionOptions = args.length ? args.shift() || {} : {}; |
| 1172 | ||
| 1173 | 0 | this._executeQueryCommand(DbCommand.createGetLastErrorCommand(options, this), connectionOptions, function(err, error) { |
| 1174 | 0 | callback(err, error && error.documents); |
| 1175 | }); | |
| 1176 | }; | |
| 1177 | ||
| 1178 | /** | |
| 1179 | * Legacy method calls. | |
| 1180 | * | |
| 1181 | * @ignore | |
| 1182 | * @api private | |
| 1183 | */ | |
| 1184 | 1 | Db.prototype.error = Db.prototype.lastError; |
| 1185 | 1 | Db.prototype.lastStatus = Db.prototype.lastError; |
| 1186 | ||
| 1187 | /** | |
| 1188 | * Return all errors up to the last time db reset_error_history was called. | |
| 1189 | * | |
| 1190 | * Options | |
| 1191 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1192 | * | |
| 1193 | * @param {Object} [options] returns option results. | |
| 1194 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from previousErrors or null if an error occurred. | |
| 1195 | * @return {null} | |
| 1196 | * @api public | |
| 1197 | */ | |
| 1198 | 1 | Db.prototype.previousErrors = function(options, callback) { |
| 1199 | // Unpack calls | |
| 1200 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1201 | 0 | callback = args.pop(); |
| 1202 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1203 | ||
| 1204 | 0 | this._executeQueryCommand(DbCommand.createGetPreviousErrorsCommand(this), options, function(err, error) { |
| 1205 | 0 | callback(err, error.documents); |
| 1206 | }); | |
| 1207 | }; | |
| 1208 | ||
| 1209 | /** | |
| 1210 | * Runs a command on the database. | |
| 1211 | * @ignore | |
| 1212 | * @api private | |
| 1213 | */ | |
| 1214 | 1 | Db.prototype.executeDbCommand = function(command_hash, options, callback) { |
| 1215 | 0 | if(callback == null) { callback = options; options = {}; } |
| 1216 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, command_hash, options), options, function(err, result) { |
| 1217 | 0 | if(callback) callback(err, result); |
| 1218 | }); | |
| 1219 | }; | |
| 1220 | ||
| 1221 | /** | |
| 1222 | * Runs a command on the database as admin. | |
| 1223 | * @ignore | |
| 1224 | * @api private | |
| 1225 | */ | |
| 1226 | 1 | Db.prototype.executeDbAdminCommand = function(command_hash, options, callback) { |
| 1227 | 0 | if(typeof options == 'function') { |
| 1228 | 0 | callback = options; |
| 1229 | 0 | options = {} |
| 1230 | } | |
| 1231 | ||
| 1232 | 0 | if(options.readPreference) { |
| 1233 | 0 | options.read = options.readPreference; |
| 1234 | } | |
| 1235 | ||
| 1236 | 0 | this._executeQueryCommand(DbCommand.createAdminDbCommand(this, command_hash), options, function(err, result) { |
| 1237 | 0 | if(callback) callback(err, result); |
| 1238 | }); | |
| 1239 | }; | |
| 1240 | ||
| 1241 | /** | |
| 1242 | * Resets the error history of the mongo instance. | |
| 1243 | * | |
| 1244 | * Options | |
| 1245 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1246 | * | |
| 1247 | * @param {Object} [options] returns option results. | |
| 1248 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from resetErrorHistory or null if an error occurred. | |
| 1249 | * @return {null} | |
| 1250 | * @api public | |
| 1251 | */ | |
| 1252 | 1 | Db.prototype.resetErrorHistory = function(options, callback) { |
| 1253 | // Unpack calls | |
| 1254 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1255 | 0 | callback = args.pop(); |
| 1256 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1257 | ||
| 1258 | 0 | this._executeQueryCommand(DbCommand.createResetErrorHistoryCommand(this), options, function(err, error) { |
| 1259 | 0 | if(callback) callback(err, error && error.documents); |
| 1260 | }); | |
| 1261 | }; | |
| 1262 | ||
| 1263 | /** | |
| 1264 | * Creates an index on the collection. | |
| 1265 | * | |
| 1266 | * Options | |
| 1267 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 1268 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 1269 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 1270 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 1271 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 1272 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 1273 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 1274 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 1275 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 1276 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 1277 | * - **v** {Number}, specify the format version of the indexes. | |
| 1278 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 1279 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 1280 | * | |
| 1281 | * Deprecated Options | |
| 1282 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 1283 | * | |
| 1284 | * | |
| 1285 | * @param {String} collectionName name of the collection to create the index on. | |
| 1286 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 1287 | * @param {Object} [options] additional options during update. | |
| 1288 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from createIndex or null if an error occurred. | |
| 1289 | * @return {null} | |
| 1290 | * @api public | |
| 1291 | */ | |
| 1292 | 1 | Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callback) { |
| 1293 | 0 | var self = this; |
| 1294 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1295 | 0 | callback = args.pop(); |
| 1296 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1297 | 0 | options = typeof callback === 'function' ? options : callback; |
| 1298 | 0 | options = options == null ? {} : options; |
| 1299 | ||
| 1300 | // Get the error options | |
| 1301 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 1302 | // Create command | |
| 1303 | 0 | var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); |
| 1304 | // Default command options | |
| 1305 | 0 | var commandOptions = {}; |
| 1306 | ||
| 1307 | // If we have error conditions set handle them | |
| 1308 | 0 | if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 1309 | // Insert options | |
| 1310 | 0 | commandOptions['read'] = false; |
| 1311 | // If we have safe set set async to false | |
| 1312 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 1313 | ||
| 1314 | // Set safe option | |
| 1315 | 0 | commandOptions['safe'] = errorOptions; |
| 1316 | // If we have an error option | |
| 1317 | 0 | if(typeof errorOptions == 'object') { |
| 1318 | 0 | var keys = Object.keys(errorOptions); |
| 1319 | 0 | for(var i = 0; i < keys.length; i++) { |
| 1320 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 1321 | } | |
| 1322 | } | |
| 1323 | ||
| 1324 | // Execute insert command | |
| 1325 | 0 | this._executeInsertCommand(command, commandOptions, function(err, result) { |
| 1326 | 0 | if(err != null) return callback(err, null); |
| 1327 | ||
| 1328 | 0 | result = result && result.documents; |
| 1329 | 0 | if (result[0].err) { |
| 1330 | 0 | callback(utils.toError(result[0])); |
| 1331 | } else { | |
| 1332 | 0 | callback(null, command.documents[0].name); |
| 1333 | } | |
| 1334 | }); | |
| 1335 | 0 | } else if(_hasWriteConcern(errorOptions) && callback == null) { |
| 1336 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 1337 | } else { | |
| 1338 | // Execute insert command | |
| 1339 | 0 | var result = this._executeInsertCommand(command, commandOptions); |
| 1340 | // If no callback just return | |
| 1341 | 0 | if(!callback) return; |
| 1342 | // If error return error | |
| 1343 | 0 | if(result instanceof Error) { |
| 1344 | 0 | return callback(result); |
| 1345 | } | |
| 1346 | // Otherwise just return | |
| 1347 | 0 | return callback(null, null); |
| 1348 | } | |
| 1349 | }; | |
| 1350 | ||
| 1351 | /** | |
| 1352 | * Ensures that an index exists, if it does not it creates it | |
| 1353 | * | |
| 1354 | * Options | |
| 1355 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 1356 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 1357 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 1358 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 1359 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 1360 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 1361 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 1362 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 1363 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 1364 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 1365 | * - **v** {Number}, specify the format version of the indexes. | |
| 1366 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 1367 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 1368 | * | |
| 1369 | * Deprecated Options | |
| 1370 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 1371 | * | |
| 1372 | * @param {String} collectionName name of the collection to create the index on. | |
| 1373 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 1374 | * @param {Object} [options] additional options during update. | |
| 1375 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from ensureIndex or null if an error occurred. | |
| 1376 | * @return {null} | |
| 1377 | * @api public | |
| 1378 | */ | |
| 1379 | 1 | Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callback) { |
| 1380 | 0 | var self = this; |
| 1381 | ||
| 1382 | 0 | if (typeof callback === 'undefined' && typeof options === 'function') { |
| 1383 | 0 | callback = options; |
| 1384 | 0 | options = {}; |
| 1385 | } | |
| 1386 | ||
| 1387 | 0 | if (options == null) { |
| 1388 | 0 | options = {}; |
| 1389 | } | |
| 1390 | ||
| 1391 | // Get the error options | |
| 1392 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 1393 | // Make sure we don't try to do a write concern without a callback | |
| 1394 | 0 | if(_hasWriteConcern(errorOptions) && callback == null) |
| 1395 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 1396 | // Create command | |
| 1397 | 0 | var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); |
| 1398 | 0 | var index_name = command.documents[0].name; |
| 1399 | ||
| 1400 | // Default command options | |
| 1401 | 0 | var commandOptions = {}; |
| 1402 | // Check if the index allready exists | |
| 1403 | 0 | this.indexInformation(collectionName, function(err, collectionInfo) { |
| 1404 | 0 | if(err != null) return callback(err, null); |
| 1405 | ||
| 1406 | 0 | if(!collectionInfo[index_name]) { |
| 1407 | // If we have error conditions set handle them | |
| 1408 | 0 | if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 1409 | // Insert options | |
| 1410 | 0 | commandOptions['read'] = false; |
| 1411 | // If we have safe set set async to false | |
| 1412 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 1413 | ||
| 1414 | // If we have an error option | |
| 1415 | 0 | if(typeof errorOptions == 'object') { |
| 1416 | 0 | var keys = Object.keys(errorOptions); |
| 1417 | 0 | for(var i = 0; i < keys.length; i++) { |
| 1418 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 1419 | } | |
| 1420 | } | |
| 1421 | ||
| 1422 | 0 | if(typeof callback === 'function' |
| 1423 | && commandOptions.w < 1 && !commandOptions.fsync && !commandOptions.journal) { | |
| 1424 | 0 | commandOptions.w = 1; |
| 1425 | } | |
| 1426 | ||
| 1427 | 0 | self._executeInsertCommand(command, commandOptions, function(err, result) { |
| 1428 | // Only callback if we have one specified | |
| 1429 | 0 | if(typeof callback === 'function') { |
| 1430 | 0 | if(err != null) return callback(err, null); |
| 1431 | ||
| 1432 | 0 | result = result && result.documents; |
| 1433 | 0 | if (result[0].err) { |
| 1434 | 0 | callback(utils.toError(result[0])); |
| 1435 | } else { | |
| 1436 | 0 | callback(null, command.documents[0].name); |
| 1437 | } | |
| 1438 | } | |
| 1439 | }); | |
| 1440 | } else { | |
| 1441 | // Execute insert command | |
| 1442 | 0 | var result = self._executeInsertCommand(command, commandOptions); |
| 1443 | // If no callback just return | |
| 1444 | 0 | if(!callback) return; |
| 1445 | // If error return error | |
| 1446 | 0 | if(result instanceof Error) { |
| 1447 | 0 | return callback(result); |
| 1448 | } | |
| 1449 | // Otherwise just return | |
| 1450 | 0 | return callback(null, index_name); |
| 1451 | } | |
| 1452 | } else { | |
| 1453 | 0 | if(typeof callback === 'function') return callback(null, index_name); |
| 1454 | } | |
| 1455 | }); | |
| 1456 | }; | |
| 1457 | ||
| 1458 | /** | |
| 1459 | * Returns the information available on allocated cursors. | |
| 1460 | * | |
| 1461 | * Options | |
| 1462 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 1463 | * | |
| 1464 | * @param {Object} [options] additional options during update. | |
| 1465 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from cursorInfo or null if an error occurred. | |
| 1466 | * @return {null} | |
| 1467 | * @api public | |
| 1468 | */ | |
| 1469 | 1 | Db.prototype.cursorInfo = function(options, callback) { |
| 1470 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1471 | 0 | callback = args.pop(); |
| 1472 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1473 | ||
| 1474 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, {'cursorInfo':1}) |
| 1475 | , options | |
| 1476 | , utils.handleSingleCommandResultReturn(null, null, callback)); | |
| 1477 | }; | |
| 1478 | ||
| 1479 | /** | |
| 1480 | * Drop an index on a collection. | |
| 1481 | * | |
| 1482 | * @param {String} collectionName the name of the collection where the command will drop an index. | |
| 1483 | * @param {String} indexName name of the index to drop. | |
| 1484 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropIndex or null if an error occurred. | |
| 1485 | * @return {null} | |
| 1486 | * @api public | |
| 1487 | */ | |
| 1488 | 1 | Db.prototype.dropIndex = function(collectionName, indexName, callback) { |
| 1489 | 0 | this._executeQueryCommand(DbCommand.createDropIndexCommand(this, collectionName, indexName) |
| 1490 | , utils.handleSingleCommandResultReturn(null, null, callback)); | |
| 1491 | }; | |
| 1492 | ||
| 1493 | /** | |
| 1494 | * Reindex all indexes on the collection | |
| 1495 | * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. | |
| 1496 | * | |
| 1497 | * @param {String} collectionName the name of the collection. | |
| 1498 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from reIndex or null if an error occurred. | |
| 1499 | * @api public | |
| 1500 | **/ | |
| 1501 | 1 | Db.prototype.reIndex = function(collectionName, callback) { |
| 1502 | 0 | this._executeQueryCommand(DbCommand.createReIndexCommand(this, collectionName) |
| 1503 | , utils.handleSingleCommandResultReturn(true, false, callback)); | |
| 1504 | }; | |
| 1505 | ||
| 1506 | /** | |
| 1507 | * Retrieves this collections index info. | |
| 1508 | * | |
| 1509 | * Options | |
| 1510 | * - **full** {Boolean, default:false}, returns the full raw index information. | |
| 1511 | * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). | |
| 1512 | * | |
| 1513 | * @param {String} collectionName the name of the collection. | |
| 1514 | * @param {Object} [options] additional options during update. | |
| 1515 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from indexInformation or null if an error occurred. | |
| 1516 | * @return {null} | |
| 1517 | * @api public | |
| 1518 | */ | |
| 1519 | 1 | Db.prototype.indexInformation = function(collectionName, options, callback) { |
| 1520 | 0 | if(typeof callback === 'undefined') { |
| 1521 | 0 | if(typeof options === 'undefined') { |
| 1522 | 0 | callback = collectionName; |
| 1523 | 0 | collectionName = null; |
| 1524 | } else { | |
| 1525 | 0 | callback = options; |
| 1526 | } | |
| 1527 | 0 | options = {}; |
| 1528 | } | |
| 1529 | ||
| 1530 | // If we specified full information | |
| 1531 | 0 | var full = options['full'] == null ? false : options['full']; |
| 1532 | // Build selector for the indexes | |
| 1533 | 0 | var selector = collectionName != null ? {ns: (this.databaseName + "." + collectionName)} : {}; |
| 1534 | ||
| 1535 | // Set read preference if we set one | |
| 1536 | 0 | var readPreference = options['readPreference'] ? options['readPreference'] : ReadPreference.PRIMARY; |
| 1537 | ||
| 1538 | // Iterate through all the fields of the index | |
| 1539 | 0 | this.collection(DbCommand.SYSTEM_INDEX_COLLECTION, function(err, collection) { |
| 1540 | // Perform the find for the collection | |
| 1541 | 0 | collection.find(selector).setReadPreference(readPreference).toArray(function(err, indexes) { |
| 1542 | 0 | if(err != null) return callback(err, null); |
| 1543 | // Contains all the information | |
| 1544 | 0 | var info = {}; |
| 1545 | ||
| 1546 | // if full defined just return all the indexes directly | |
| 1547 | 0 | if(full) return callback(null, indexes); |
| 1548 | ||
| 1549 | // Process all the indexes | |
| 1550 | 0 | for(var i = 0; i < indexes.length; i++) { |
| 1551 | 0 | var index = indexes[i]; |
| 1552 | // Let's unpack the object | |
| 1553 | 0 | info[index.name] = []; |
| 1554 | 0 | for(var name in index.key) { |
| 1555 | 0 | info[index.name].push([name, index.key[name]]); |
| 1556 | } | |
| 1557 | } | |
| 1558 | ||
| 1559 | // Return all the indexes | |
| 1560 | 0 | callback(null, info); |
| 1561 | }); | |
| 1562 | }); | |
| 1563 | }; | |
| 1564 | ||
| 1565 | /** | |
| 1566 | * Drop a database. | |
| 1567 | * | |
| 1568 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropDatabase or null if an error occurred. | |
| 1569 | * @return {null} | |
| 1570 | * @api public | |
| 1571 | */ | |
| 1572 | 1 | Db.prototype.dropDatabase = function(callback) { |
| 1573 | 0 | this._executeQueryCommand(DbCommand.createDropDatabaseCommand(this) |
| 1574 | , utils.handleSingleCommandResultReturn(true, false, callback)); | |
| 1575 | } | |
| 1576 | ||
| 1577 | /** | |
| 1578 | * Get all the db statistics. | |
| 1579 | * | |
| 1580 | * Options | |
| 1581 | * - **scale** {Number}, divide the returned sizes by scale value. | |
| 1582 | * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). | |
| 1583 | * | |
| 1584 | * @param {Objects} [options] options for the stats command | |
| 1585 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from stats or null if an error occurred. | |
| 1586 | * @return {null} | |
| 1587 | * @api public | |
| 1588 | */ | |
| 1589 | 1 | Db.prototype.stats = function stats(options, callback) { |
| 1590 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1591 | 0 | callback = args.pop(); |
| 1592 | // Fetch all commands | |
| 1593 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1594 | ||
| 1595 | // Build command object | |
| 1596 | 0 | var commandObject = { |
| 1597 | dbStats:this.collectionName | |
| 1598 | }; | |
| 1599 | ||
| 1600 | // Check if we have the scale value | |
| 1601 | 0 | if(options['scale'] != null) commandObject['scale'] = options['scale']; |
| 1602 | ||
| 1603 | // Execute the command | |
| 1604 | 0 | this.command(commandObject, options, callback); |
| 1605 | } | |
| 1606 | ||
| 1607 | /** | |
| 1608 | * @ignore | |
| 1609 | */ | |
| 1610 | 1 | var __executeQueryCommand = function(self, db_command, options, callback) { |
| 1611 | // Options unpacking | |
| 1612 | 0 | var read = options['read'] != null ? options['read'] : false; |
| 1613 | 0 | read = options['readPreference'] != null && options['read'] == null ? options['readPreference'] : read; |
| 1614 | 0 | var raw = options['raw'] != null ? options['raw'] : self.raw; |
| 1615 | 0 | var onAll = options['onAll'] != null ? options['onAll'] : false; |
| 1616 | 0 | var specifiedConnection = options['connection'] != null ? options['connection'] : null; |
| 1617 | ||
| 1618 | // Correct read preference to default primary if set to false, null or primary | |
| 1619 | 0 | if(!(typeof read == 'object') && read._type == 'ReadPreference') { |
| 1620 | 0 | read = (read == null || read == 'primary' || read == false) ? ReadPreference.PRIMARY : read; |
| 1621 | 0 | if(!ReadPreference.isValid(read)) return callback(new Error("Illegal readPreference mode specified, " + read)); |
| 1622 | 0 | } else if(typeof read == 'object' && read._type == 'ReadPreference') { |
| 1623 | 0 | if(!read.isValid()) return callback(new Error("Illegal readPreference mode specified, " + read.mode)); |
| 1624 | } | |
| 1625 | ||
| 1626 | // If we have a read preference set and we are a mongos pass the read preference on to the mongos instance, | |
| 1627 | 0 | if(self.serverConfig.isMongos() && read != null && read != false) { |
| 1628 | 0 | db_command.setMongosReadPreference(read); |
| 1629 | } | |
| 1630 | ||
| 1631 | // If we got a callback object | |
| 1632 | 0 | if(typeof callback === 'function' && !onAll) { |
| 1633 | // Override connection if we passed in a specific connection | |
| 1634 | 0 | var connection = specifiedConnection != null ? specifiedConnection : null; |
| 1635 | ||
| 1636 | 0 | if(connection instanceof Error) return callback(connection, null); |
| 1637 | ||
| 1638 | // Fetch either a reader or writer dependent on the specified read option if no connection | |
| 1639 | // was passed in | |
| 1640 | 0 | if(connection == null) { |
| 1641 | 0 | connection = self.serverConfig.checkoutReader(read); |
| 1642 | } | |
| 1643 | ||
| 1644 | 0 | if(connection == null) { |
| 1645 | 0 | return callback(new Error("no open connections")); |
| 1646 | 0 | } else if(connection instanceof Error || connection['message'] != null) { |
| 1647 | 0 | return callback(connection); |
| 1648 | } | |
| 1649 | ||
| 1650 | // Exhaust Option | |
| 1651 | 0 | var exhaust = options.exhaust || false; |
| 1652 | ||
| 1653 | // Register the handler in the data structure | |
| 1654 | 0 | self.serverConfig._registerHandler(db_command, raw, connection, exhaust, callback); |
| 1655 | ||
| 1656 | // Write the message out and handle any errors if there are any | |
| 1657 | 0 | connection.write(db_command, function(err) { |
| 1658 | 0 | if(err != null) { |
| 1659 | // Call the handler with an error | |
| 1660 | 0 | if(Array.isArray(db_command)) |
| 1661 | 0 | self.serverConfig._callHandler(db_command[0].getRequestId(), null, err); |
| 1662 | else | |
| 1663 | 0 | self.serverConfig._callHandler(db_command.getRequestId(), null, err); |
| 1664 | } | |
| 1665 | }); | |
| 1666 | 0 | } else if(typeof callback === 'function' && onAll) { |
| 1667 | 0 | var connections = self.serverConfig.allRawConnections(); |
| 1668 | 0 | var numberOfEntries = connections.length; |
| 1669 | // Go through all the connections | |
| 1670 | 0 | for(var i = 0; i < connections.length; i++) { |
| 1671 | // Fetch a connection | |
| 1672 | 0 | var connection = connections[i]; |
| 1673 | ||
| 1674 | // Ensure we have a valid connection | |
| 1675 | 0 | if(connection == null) { |
| 1676 | 0 | return callback(new Error("no open connections")); |
| 1677 | 0 | } else if(connection instanceof Error) { |
| 1678 | 0 | return callback(connection); |
| 1679 | } | |
| 1680 | ||
| 1681 | // Register the handler in the data structure | |
| 1682 | 0 | self.serverConfig._registerHandler(db_command, raw, connection, callback); |
| 1683 | ||
| 1684 | // Write the message out | |
| 1685 | 0 | connection.write(db_command, function(err) { |
| 1686 | // Adjust the number of entries we need to process | |
| 1687 | 0 | numberOfEntries = numberOfEntries - 1; |
| 1688 | // Remove listener | |
| 1689 | 0 | if(err != null) { |
| 1690 | // Clean up listener and return error | |
| 1691 | 0 | self.serverConfig._removeHandler(db_command.getRequestId()); |
| 1692 | } | |
| 1693 | ||
| 1694 | // No more entries to process callback with the error | |
| 1695 | 0 | if(numberOfEntries <= 0) { |
| 1696 | 0 | callback(err); |
| 1697 | } | |
| 1698 | }); | |
| 1699 | ||
| 1700 | // Update the db_command request id | |
| 1701 | 0 | db_command.updateRequestId(); |
| 1702 | } | |
| 1703 | } else { | |
| 1704 | // Fetch either a reader or writer dependent on the specified read option | |
| 1705 | // var connection = read == null || read == 'primary' || read == false ? self.serverConfig.checkoutWriter(true) : self.serverConfig.checkoutReader(read); | |
| 1706 | 0 | var connection = self.serverConfig.checkoutReader(read); |
| 1707 | // Override connection if needed | |
| 1708 | 0 | connection = specifiedConnection != null ? specifiedConnection : connection; |
| 1709 | // Ensure we have a valid connection | |
| 1710 | 0 | if(connection == null || connection instanceof Error || connection['message'] != null) return null; |
| 1711 | // Write the message out | |
| 1712 | 0 | connection.write(db_command, function(err) { |
| 1713 | 0 | if(err != null) { |
| 1714 | // Emit the error | |
| 1715 | 0 | self.emit("error", err); |
| 1716 | } | |
| 1717 | }); | |
| 1718 | } | |
| 1719 | }; | |
| 1720 | ||
| 1721 | /** | |
| 1722 | * Execute db query command (not safe) | |
| 1723 | * @ignore | |
| 1724 | * @api private | |
| 1725 | */ | |
| 1726 | 1 | Db.prototype._executeQueryCommand = function(db_command, options, callback) { |
| 1727 | 0 | var self = this; |
| 1728 | ||
| 1729 | // Unpack the parameters | |
| 1730 | 0 | if (typeof callback === 'undefined') { |
| 1731 | 0 | callback = options; |
| 1732 | 0 | options = {}; |
| 1733 | } | |
| 1734 | ||
| 1735 | // fast fail option used for HA, no retry | |
| 1736 | 0 | var failFast = options['failFast'] != null |
| 1737 | ? options['failFast'] | |
| 1738 | : false; | |
| 1739 | ||
| 1740 | // Check if the user force closed the command | |
| 1741 | 0 | if(this._applicationClosed) { |
| 1742 | 0 | var err = new Error("db closed by application"); |
| 1743 | 0 | if('function' == typeof callback) { |
| 1744 | 0 | return callback(err, null); |
| 1745 | } else { | |
| 1746 | 0 | throw err; |
| 1747 | } | |
| 1748 | } | |
| 1749 | ||
| 1750 | 0 | if(this.serverConfig.isDestroyed()) |
| 1751 | 0 | return callback(new Error("Connection was destroyed by application")); |
| 1752 | ||
| 1753 | // Specific connection | |
| 1754 | 0 | var connection = options.connection; |
| 1755 | // Check if the connection is actually live | |
| 1756 | 0 | if(connection |
| 1757 | 0 | && (!connection.isConnected || !connection.isConnected())) connection = null; |
| 1758 | ||
| 1759 | // Get the configuration | |
| 1760 | 0 | var config = this.serverConfig; |
| 1761 | 0 | var read = options.read; |
| 1762 | // Allow for the usage of the readPreference model | |
| 1763 | 0 | if(read == null) { |
| 1764 | 0 | read = options.readPreference; |
| 1765 | } | |
| 1766 | ||
| 1767 | 0 | if(!connection && !config.canRead(read) && !config.canWrite() && config.isAutoReconnect()) { |
| 1768 | 0 | if(read == ReadPreference.PRIMARY |
| 1769 | || read == ReadPreference.PRIMARY_PREFERRED | |
| 1770 | || (read != null && typeof read == 'object' && read.mode) | |
| 1771 | || read == null) { | |
| 1772 | ||
| 1773 | // Save the command | |
| 1774 | 0 | self.serverConfig._commandsStore.read_from_writer( |
| 1775 | { type: 'query' | |
| 1776 | , db_command: db_command | |
| 1777 | , options: options | |
| 1778 | , callback: callback | |
| 1779 | , db: self | |
| 1780 | , executeQueryCommand: __executeQueryCommand | |
| 1781 | , executeInsertCommand: __executeInsertCommand | |
| 1782 | } | |
| 1783 | ); | |
| 1784 | } else { | |
| 1785 | 0 | self.serverConfig._commandsStore.read( |
| 1786 | { type: 'query' | |
| 1787 | , db_command: db_command | |
| 1788 | , options: options | |
| 1789 | , callback: callback | |
| 1790 | , db: self | |
| 1791 | , executeQueryCommand: __executeQueryCommand | |
| 1792 | , executeInsertCommand: __executeInsertCommand | |
| 1793 | } | |
| 1794 | ); | |
| 1795 | } | |
| 1796 | ||
| 1797 | // If we have blown through the number of items let's | |
| 1798 | 0 | if(!self.serverConfig._commandsStore.validateBufferLimit(self.bufferMaxEntries)) { |
| 1799 | 0 | self.close(); |
| 1800 | } | |
| 1801 | 0 | } else if(!connection && !config.canRead(read) && !config.canWrite() && !config.isAutoReconnect()) { |
| 1802 | 0 | return callback(new Error("no open connections"), null); |
| 1803 | } else { | |
| 1804 | 0 | if(typeof callback == 'function') { |
| 1805 | 0 | __executeQueryCommand(self, db_command, options, function (err, result, conn) { |
| 1806 | 0 | callback(err, result, conn); |
| 1807 | }); | |
| 1808 | } else { | |
| 1809 | 0 | __executeQueryCommand(self, db_command, options); |
| 1810 | } | |
| 1811 | } | |
| 1812 | }; | |
| 1813 | ||
| 1814 | /** | |
| 1815 | * @ignore | |
| 1816 | */ | |
| 1817 | 1 | var __executeInsertCommand = function(self, db_command, options, callback) { |
| 1818 | // Always checkout a writer for this kind of operations | |
| 1819 | 0 | var connection = self.serverConfig.checkoutWriter(); |
| 1820 | // Get safe mode | |
| 1821 | 0 | var safe = options['safe'] != null ? options['safe'] : false; |
| 1822 | 0 | var raw = options['raw'] != null ? options['raw'] : self.raw; |
| 1823 | 0 | var specifiedConnection = options['connection'] != null ? options['connection'] : null; |
| 1824 | // Override connection if needed | |
| 1825 | 0 | connection = specifiedConnection != null ? specifiedConnection : connection; |
| 1826 | ||
| 1827 | // Validate if we can use this server 2.6 wire protocol | |
| 1828 | 0 | if(!connection.isCompatible()) { |
| 1829 | 0 | return callback(utils.toError("driver is incompatible with this server version"), null); |
| 1830 | } | |
| 1831 | ||
| 1832 | // Ensure we have a valid connection | |
| 1833 | 0 | if(typeof callback === 'function') { |
| 1834 | // Ensure we have a valid connection | |
| 1835 | 0 | if(connection == null) { |
| 1836 | 0 | return callback(new Error("no open connections")); |
| 1837 | 0 | } else if(connection instanceof Error) { |
| 1838 | 0 | return callback(connection); |
| 1839 | } | |
| 1840 | ||
| 1841 | 0 | var errorOptions = _getWriteConcern(self, options); |
| 1842 | 0 | if(errorOptions.w > 0 || errorOptions.w == 'majority' || errorOptions.j || errorOptions.journal || errorOptions.fsync) { |
| 1843 | // db command is now an array of commands (original command + lastError) | |
| 1844 | 0 | db_command = [db_command, DbCommand.createGetLastErrorCommand(safe, self)]; |
| 1845 | // Register the handler in the data structure | |
| 1846 | 0 | self.serverConfig._registerHandler(db_command[1], raw, connection, callback); |
| 1847 | } | |
| 1848 | } | |
| 1849 | ||
| 1850 | // If we have no callback and there is no connection | |
| 1851 | 0 | if(connection == null) return null; |
| 1852 | 0 | if(connection instanceof Error && typeof callback == 'function') return callback(connection, null); |
| 1853 | 0 | if(connection instanceof Error) return null; |
| 1854 | 0 | if(connection == null && typeof callback == 'function') return callback(new Error("no primary server found"), null); |
| 1855 | ||
| 1856 | // Write the message out | |
| 1857 | 0 | connection.write(db_command, function(err) { |
| 1858 | // Return the callback if it's not a safe operation and the callback is defined | |
| 1859 | 0 | if(typeof callback === 'function' && (safe == null || safe == false)) { |
| 1860 | // Perform the callback | |
| 1861 | 0 | callback(err, null); |
| 1862 | 0 | } else if(typeof callback === 'function') { |
| 1863 | // Call the handler with an error | |
| 1864 | 0 | self.serverConfig._callHandler(db_command[1].getRequestId(), null, err); |
| 1865 | 0 | } else if(typeof callback == 'function' && safe && safe.w == -1) { |
| 1866 | // Call the handler with no error | |
| 1867 | 0 | self.serverConfig._callHandler(db_command[1].getRequestId(), null, null); |
| 1868 | 0 | } else if(!safe || safe.w == -1) { |
| 1869 | 0 | self.emit("error", err); |
| 1870 | } | |
| 1871 | }); | |
| 1872 | }; | |
| 1873 | ||
| 1874 | /** | |
| 1875 | * Execute an insert Command | |
| 1876 | * @ignore | |
| 1877 | * @api private | |
| 1878 | */ | |
| 1879 | 1 | Db.prototype._executeInsertCommand = function(db_command, options, callback) { |
| 1880 | 0 | var self = this; |
| 1881 | ||
| 1882 | // Unpack the parameters | |
| 1883 | 0 | if(callback == null && typeof options === 'function') { |
| 1884 | 0 | callback = options; |
| 1885 | 0 | options = {}; |
| 1886 | } | |
| 1887 | ||
| 1888 | // Ensure options are not null | |
| 1889 | 0 | options = options == null ? {} : options; |
| 1890 | ||
| 1891 | // Check if the user force closed the command | |
| 1892 | 0 | if(this._applicationClosed) { |
| 1893 | 0 | if(typeof callback == 'function') { |
| 1894 | 0 | return callback(new Error("db closed by application"), null); |
| 1895 | } else { | |
| 1896 | 0 | throw new Error("db closed by application"); |
| 1897 | } | |
| 1898 | } | |
| 1899 | ||
| 1900 | 0 | if(this.serverConfig.isDestroyed()) return callback(new Error("Connection was destroyed by application")); |
| 1901 | ||
| 1902 | // Specific connection | |
| 1903 | 0 | var connection = options.connection; |
| 1904 | // Check if the connection is actually live | |
| 1905 | 0 | if(connection |
| 1906 | 0 | && (!connection.isConnected || !connection.isConnected())) connection = null; |
| 1907 | ||
| 1908 | // Get config | |
| 1909 | 0 | var config = self.serverConfig; |
| 1910 | // Check if we are connected | |
| 1911 | 0 | if(!connection && !config.canWrite() && config.isAutoReconnect()) { |
| 1912 | 0 | self.serverConfig._commandsStore.write( |
| 1913 | { type:'insert' | |
| 1914 | , 'db_command':db_command | |
| 1915 | , 'options':options | |
| 1916 | , 'callback':callback | |
| 1917 | , db: self | |
| 1918 | , executeQueryCommand: __executeQueryCommand | |
| 1919 | , executeInsertCommand: __executeInsertCommand | |
| 1920 | } | |
| 1921 | ); | |
| 1922 | ||
| 1923 | // If we have blown through the number of items let's | |
| 1924 | 0 | if(!self.serverConfig._commandsStore.validateBufferLimit(self.bufferMaxEntries)) { |
| 1925 | 0 | self.close(); |
| 1926 | } | |
| 1927 | 0 | } else if(!connection && !config.canWrite() && !config.isAutoReconnect()) { |
| 1928 | 0 | return callback(new Error("no open connections"), null); |
| 1929 | } else { | |
| 1930 | 0 | __executeInsertCommand(self, db_command, options, callback); |
| 1931 | } | |
| 1932 | }; | |
| 1933 | ||
| 1934 | /** | |
| 1935 | * Update command is the same | |
| 1936 | * @ignore | |
| 1937 | * @api private | |
| 1938 | */ | |
| 1939 | 1 | Db.prototype._executeUpdateCommand = Db.prototype._executeInsertCommand; |
| 1940 | /** | |
| 1941 | * Remove command is the same | |
| 1942 | * @ignore | |
| 1943 | * @api private | |
| 1944 | */ | |
| 1945 | 1 | Db.prototype._executeRemoveCommand = Db.prototype._executeInsertCommand; |
| 1946 | ||
| 1947 | /** | |
| 1948 | * Wrap a Mongo error document into an Error instance. | |
| 1949 | * Deprecated. Use utils.toError instead. | |
| 1950 | * | |
| 1951 | * @ignore | |
| 1952 | * @api private | |
| 1953 | * @deprecated | |
| 1954 | */ | |
| 1955 | 1 | Db.prototype.wrap = utils.toError; |
| 1956 | ||
| 1957 | /** | |
| 1958 | * Default URL | |
| 1959 | * | |
| 1960 | * @classconstant DEFAULT_URL | |
| 1961 | **/ | |
| 1962 | 1 | Db.DEFAULT_URL = 'mongodb://localhost:27017/default'; |
| 1963 | ||
| 1964 | /** | |
| 1965 | * Connect to MongoDB using a url as documented at | |
| 1966 | * | |
| 1967 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 1968 | * | |
| 1969 | * Options | |
| 1970 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 1971 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 1972 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 1973 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 1974 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 1975 | * | |
| 1976 | * @param {String} url connection url for MongoDB. | |
| 1977 | * @param {Object} [options] optional options for insert command | |
| 1978 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the db instance or null if an error occurred. | |
| 1979 | * @return {null} | |
| 1980 | * @api public | |
| 1981 | */ | |
| 1982 | 1 | Db.connect = function(url, options, callback) { |
| 1983 | // Ensure correct mapping of the callback | |
| 1984 | 0 | if(typeof options == 'function') { |
| 1985 | 0 | callback = options; |
| 1986 | 0 | options = {}; |
| 1987 | } | |
| 1988 | ||
| 1989 | // Ensure same behavior as previous version w:0 | |
| 1990 | 0 | if(url.indexOf("safe") == -1 |
| 1991 | && url.indexOf("w") == -1 | |
| 1992 | && url.indexOf("journal") == -1 && url.indexOf("j") == -1 | |
| 1993 | 0 | && url.indexOf("fsync") == -1) options.w = 0; |
| 1994 | ||
| 1995 | // Avoid circular require problem | |
| 1996 | 0 | var MongoClient = require('./mongo_client.js').MongoClient; |
| 1997 | // Attempt to connect | |
| 1998 | 0 | MongoClient.connect.call(MongoClient, url, options, callback); |
| 1999 | }; | |
| 2000 | ||
| 2001 | /** | |
| 2002 | * State of the db connection | |
| 2003 | * @ignore | |
| 2004 | */ | |
| 2005 | 1 | Object.defineProperty(Db.prototype, "state", { enumerable: true |
| 2006 | , get: function () { | |
| 2007 | 0 | return this.serverConfig._serverState; |
| 2008 | } | |
| 2009 | }); | |
| 2010 | ||
| 2011 | /** | |
| 2012 | * @ignore | |
| 2013 | */ | |
| 2014 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 2015 | 0 | return errorOptions == true |
| 2016 | || errorOptions.w > 0 | |
| 2017 | || errorOptions.w == 'majority' | |
| 2018 | || errorOptions.j == true | |
| 2019 | || errorOptions.journal == true | |
| 2020 | || errorOptions.fsync == true | |
| 2021 | }; | |
| 2022 | ||
| 2023 | /** | |
| 2024 | * @ignore | |
| 2025 | */ | |
| 2026 | 1 | var _setWriteConcernHash = function(options) { |
| 2027 | 0 | var finalOptions = {}; |
| 2028 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 2029 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 2030 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 2031 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 2032 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 2033 | 0 | return finalOptions; |
| 2034 | }; | |
| 2035 | ||
| 2036 | /** | |
| 2037 | * @ignore | |
| 2038 | */ | |
| 2039 | 1 | var _getWriteConcern = function(self, options, callback) { |
| 2040 | // Final options | |
| 2041 | 0 | var finalOptions = {w:1}; |
| 2042 | // Local options verification | |
| 2043 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 2044 | 0 | finalOptions = _setWriteConcernHash(options); |
| 2045 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 2046 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 2047 | 0 | } else if(typeof options.safe == "boolean") { |
| 2048 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 2049 | 0 | } else if(self.options.w != null || typeof self.options.j == 'boolean' || typeof self.options.journal == 'boolean' || typeof self.options.fsync == 'boolean') { |
| 2050 | 0 | finalOptions = _setWriteConcernHash(self.options); |
| 2051 | 0 | } else if(self.safe.w != null || typeof self.safe.j == 'boolean' || typeof self.safe.journal == 'boolean' || typeof self.safe.fsync == 'boolean') { |
| 2052 | 0 | finalOptions = _setWriteConcernHash(self.safe); |
| 2053 | 0 | } else if(typeof self.safe == "boolean") { |
| 2054 | 0 | finalOptions = {w: (self.safe ? 1 : 0)}; |
| 2055 | } | |
| 2056 | ||
| 2057 | // Ensure we don't have an invalid combination of write concerns | |
| 2058 | 0 | if(finalOptions.w < 1 |
| 2059 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowledgement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 2060 | ||
| 2061 | // Return the options | |
| 2062 | 0 | return finalOptions; |
| 2063 | }; | |
| 2064 | ||
| 2065 | /** | |
| 2066 | * Legacy support | |
| 2067 | * | |
| 2068 | * @ignore | |
| 2069 | * @api private | |
| 2070 | */ | |
| 2071 | 1 | exports.connect = Db.connect; |
| 2072 | 1 | exports.Db = Db; |
| 2073 | ||
| 2074 | /** | |
| 2075 | * Remove all listeners to the db instance. | |
| 2076 | * @ignore | |
| 2077 | * @api private | |
| 2078 | */ | |
| 2079 | 1 | Db.prototype.removeAllEventListeners = function() { |
| 2080 | 0 | this.removeAllListeners("close"); |
| 2081 | 0 | this.removeAllListeners("error"); |
| 2082 | 0 | this.removeAllListeners("timeout"); |
| 2083 | 0 | this.removeAllListeners("parseError"); |
| 2084 | 0 | this.removeAllListeners("poolReady"); |
| 2085 | 0 | this.removeAllListeners("message"); |
| 2086 | }; | |
| 2087 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Binary = require('bson').Binary, |
| 2 | ObjectID = require('bson').ObjectID; | |
| 3 | ||
| 4 | /** | |
| 5 | * Class for representing a single chunk in GridFS. | |
| 6 | * | |
| 7 | * @class | |
| 8 | * | |
| 9 | * @param file {GridStore} The {@link GridStore} object holding this chunk. | |
| 10 | * @param mongoObject {object} The mongo object representation of this chunk. | |
| 11 | * | |
| 12 | * @throws Error when the type of data field for {@link mongoObject} is not | |
| 13 | * supported. Currently supported types for data field are instances of | |
| 14 | * {@link String}, {@link Array}, {@link Binary} and {@link Binary} | |
| 15 | * from the bson module | |
| 16 | * | |
| 17 | * @see Chunk#buildMongoObject | |
| 18 | */ | |
| 19 | 1 | var Chunk = exports.Chunk = function(file, mongoObject, writeConcern) { |
| 20 | 0 | if(!(this instanceof Chunk)) return new Chunk(file, mongoObject); |
| 21 | ||
| 22 | 0 | this.file = file; |
| 23 | 0 | var self = this; |
| 24 | 0 | var mongoObjectFinal = mongoObject == null ? {} : mongoObject; |
| 25 | 0 | this.writeConcern = writeConcern || {w:1}; |
| 26 | 0 | this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id; |
| 27 | 0 | this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n; |
| 28 | 0 | this.data = new Binary(); |
| 29 | ||
| 30 | 0 | if(mongoObjectFinal.data == null) { |
| 31 | 0 | } else if(typeof mongoObjectFinal.data == "string") { |
| 32 | 0 | var buffer = new Buffer(mongoObjectFinal.data.length); |
| 33 | 0 | buffer.write(mongoObjectFinal.data, 'binary', 0); |
| 34 | 0 | this.data = new Binary(buffer); |
| 35 | 0 | } else if(Array.isArray(mongoObjectFinal.data)) { |
| 36 | 0 | var buffer = new Buffer(mongoObjectFinal.data.length); |
| 37 | 0 | buffer.write(mongoObjectFinal.data.join(''), 'binary', 0); |
| 38 | 0 | this.data = new Binary(buffer); |
| 39 | 0 | } else if(mongoObjectFinal.data instanceof Binary || Object.prototype.toString.call(mongoObjectFinal.data) == "[object Binary]") { |
| 40 | 0 | this.data = mongoObjectFinal.data; |
| 41 | 0 | } else if(Buffer.isBuffer(mongoObjectFinal.data)) { |
| 42 | } else { | |
| 43 | 0 | throw Error("Illegal chunk format"); |
| 44 | } | |
| 45 | // Update position | |
| 46 | 0 | this.internalPosition = 0; |
| 47 | }; | |
| 48 | ||
| 49 | /** | |
| 50 | * Writes a data to this object and advance the read/write head. | |
| 51 | * | |
| 52 | * @param data {string} the data to write | |
| 53 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 54 | * this method. The first parameter will contain null and the second one | |
| 55 | * will contain a reference to this object. | |
| 56 | */ | |
| 57 | 1 | Chunk.prototype.write = function(data, callback) { |
| 58 | 0 | this.data.write(data, this.internalPosition); |
| 59 | 0 | this.internalPosition = this.data.length(); |
| 60 | 0 | if(callback != null) return callback(null, this); |
| 61 | 0 | return this; |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Reads data and advances the read/write head. | |
| 66 | * | |
| 67 | * @param length {number} The length of data to read. | |
| 68 | * | |
| 69 | * @return {string} The data read if the given length will not exceed the end of | |
| 70 | * the chunk. Returns an empty String otherwise. | |
| 71 | */ | |
| 72 | 1 | Chunk.prototype.read = function(length) { |
| 73 | // Default to full read if no index defined | |
| 74 | 0 | length = length == null || length == 0 ? this.length() : length; |
| 75 | ||
| 76 | 0 | if(this.length() - this.internalPosition + 1 >= length) { |
| 77 | 0 | var data = this.data.read(this.internalPosition, length); |
| 78 | 0 | this.internalPosition = this.internalPosition + length; |
| 79 | 0 | return data; |
| 80 | } else { | |
| 81 | 0 | return ''; |
| 82 | } | |
| 83 | }; | |
| 84 | ||
| 85 | 1 | Chunk.prototype.readSlice = function(length) { |
| 86 | 0 | if ((this.length() - this.internalPosition) >= length) { |
| 87 | 0 | var data = null; |
| 88 | 0 | if (this.data.buffer != null) { //Pure BSON |
| 89 | 0 | data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length); |
| 90 | } else { //Native BSON | |
| 91 | 0 | data = new Buffer(length); |
| 92 | 0 | length = this.data.readInto(data, this.internalPosition); |
| 93 | } | |
| 94 | 0 | this.internalPosition = this.internalPosition + length; |
| 95 | 0 | return data; |
| 96 | } else { | |
| 97 | 0 | return null; |
| 98 | } | |
| 99 | }; | |
| 100 | ||
| 101 | /** | |
| 102 | * Checks if the read/write head is at the end. | |
| 103 | * | |
| 104 | * @return {boolean} Whether the read/write head has reached the end of this | |
| 105 | * chunk. | |
| 106 | */ | |
| 107 | 1 | Chunk.prototype.eof = function() { |
| 108 | 0 | return this.internalPosition == this.length() ? true : false; |
| 109 | }; | |
| 110 | ||
| 111 | /** | |
| 112 | * Reads one character from the data of this chunk and advances the read/write | |
| 113 | * head. | |
| 114 | * | |
| 115 | * @return {string} a single character data read if the the read/write head is | |
| 116 | * not at the end of the chunk. Returns an empty String otherwise. | |
| 117 | */ | |
| 118 | 1 | Chunk.prototype.getc = function() { |
| 119 | 0 | return this.read(1); |
| 120 | }; | |
| 121 | ||
| 122 | /** | |
| 123 | * Clears the contents of the data in this chunk and resets the read/write head | |
| 124 | * to the initial position. | |
| 125 | */ | |
| 126 | 1 | Chunk.prototype.rewind = function() { |
| 127 | 0 | this.internalPosition = 0; |
| 128 | 0 | this.data = new Binary(); |
| 129 | }; | |
| 130 | ||
| 131 | /** | |
| 132 | * Saves this chunk to the database. Also overwrites existing entries having the | |
| 133 | * same id as this chunk. | |
| 134 | * | |
| 135 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 136 | * this method. The first parameter will contain null and the second one | |
| 137 | * will contain a reference to this object. | |
| 138 | */ | |
| 139 | 1 | Chunk.prototype.save = function(callback) { |
| 140 | 0 | var self = this; |
| 141 | ||
| 142 | 0 | self.file.chunkCollection(function(err, collection) { |
| 143 | 0 | if(err) return callback(err); |
| 144 | ||
| 145 | 0 | collection.remove({'_id':self.objectId}, self.writeConcern, function(err, result) { |
| 146 | 0 | if(err) return callback(err); |
| 147 | ||
| 148 | 0 | if(self.data.length() > 0) { |
| 149 | 0 | self.buildMongoObject(function(mongoObject) { |
| 150 | 0 | var options = {forceServerObjectId:true}; |
| 151 | 0 | for(var name in self.writeConcern) { |
| 152 | 0 | options[name] = self.writeConcern[name]; |
| 153 | } | |
| 154 | ||
| 155 | 0 | collection.insert(mongoObject, options, function(err, collection) { |
| 156 | 0 | callback(err, self); |
| 157 | }); | |
| 158 | }); | |
| 159 | } else { | |
| 160 | 0 | callback(null, self); |
| 161 | } | |
| 162 | }); | |
| 163 | }); | |
| 164 | }; | |
| 165 | ||
| 166 | /** | |
| 167 | * Creates a mongoDB object representation of this chunk. | |
| 168 | * | |
| 169 | * @param callback {function(Object)} This will be called after executing this | |
| 170 | * method. The object will be passed to the first parameter and will have | |
| 171 | * the structure: | |
| 172 | * | |
| 173 | * <pre><code> | |
| 174 | * { | |
| 175 | * '_id' : , // {number} id for this chunk | |
| 176 | * 'files_id' : , // {number} foreign key to the file collection | |
| 177 | * 'n' : , // {number} chunk number | |
| 178 | * 'data' : , // {bson#Binary} the chunk data itself | |
| 179 | * } | |
| 180 | * </code></pre> | |
| 181 | * | |
| 182 | * @see <a href="http://www.mongodb.org/display/DOCS/GridFS+Specification#GridFSSpecification-{{chunks}}">MongoDB GridFS Chunk Object Structure</a> | |
| 183 | */ | |
| 184 | 1 | Chunk.prototype.buildMongoObject = function(callback) { |
| 185 | 0 | var mongoObject = { |
| 186 | 'files_id': this.file.fileId, | |
| 187 | 'n': this.chunkNumber, | |
| 188 | 'data': this.data}; | |
| 189 | // If we are saving using a specific ObjectId | |
| 190 | 0 | if(this.objectId != null) mongoObject._id = this.objectId; |
| 191 | ||
| 192 | 0 | callback(mongoObject); |
| 193 | }; | |
| 194 | ||
| 195 | /** | |
| 196 | * @return {number} the length of the data | |
| 197 | */ | |
| 198 | 1 | Chunk.prototype.length = function() { |
| 199 | 0 | return this.data.length(); |
| 200 | }; | |
| 201 | ||
| 202 | /** | |
| 203 | * The position of the read/write head | |
| 204 | * @name position | |
| 205 | * @lends Chunk# | |
| 206 | * @field | |
| 207 | */ | |
| 208 | 1 | Object.defineProperty(Chunk.prototype, "position", { enumerable: true |
| 209 | , get: function () { | |
| 210 | 0 | return this.internalPosition; |
| 211 | } | |
| 212 | , set: function(value) { | |
| 213 | 0 | this.internalPosition = value; |
| 214 | } | |
| 215 | }); | |
| 216 | ||
| 217 | /** | |
| 218 | * The default chunk size | |
| 219 | * @constant | |
| 220 | */ | |
| 221 | 1 | Chunk.DEFAULT_CHUNK_SIZE = 1024 * 256; |
| 222 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var GridStore = require('./gridstore').GridStore, |
| 2 | ObjectID = require('bson').ObjectID; | |
| 3 | ||
| 4 | /** | |
| 5 | * A class representation of a simple Grid interface. | |
| 6 | * | |
| 7 | * @class Represents the Grid. | |
| 8 | * @param {Db} db A database instance to interact with. | |
| 9 | * @param {String} [fsName] optional different root collection for GridFS. | |
| 10 | * @return {Grid} | |
| 11 | */ | |
| 12 | 1 | function Grid(db, fsName) { |
| 13 | ||
| 14 | 0 | if(!(this instanceof Grid)) return new Grid(db, fsName); |
| 15 | ||
| 16 | 0 | this.db = db; |
| 17 | 0 | this.fsName = fsName == null ? GridStore.DEFAULT_ROOT_COLLECTION : fsName; |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Puts binary data to the grid | |
| 22 | * | |
| 23 | * Options | |
| 24 | * - **_id** {Any}, unique id for this file | |
| 25 | * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 26 | * - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. | |
| 27 | * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. | |
| 28 | * - **metadata** {Object}, arbitrary data the user wants to store. | |
| 29 | * | |
| 30 | * @param {Buffer} data buffer with Binary Data. | |
| 31 | * @param {Object} [options] the options for the files. | |
| 32 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 33 | * @return {null} | |
| 34 | * @api public | |
| 35 | */ | |
| 36 | 1 | Grid.prototype.put = function(data, options, callback) { |
| 37 | 0 | var self = this; |
| 38 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 39 | 0 | callback = args.pop(); |
| 40 | 0 | options = args.length ? args.shift() : {}; |
| 41 | // If root is not defined add our default one | |
| 42 | 0 | options['root'] = options['root'] == null ? this.fsName : options['root']; |
| 43 | ||
| 44 | // Return if we don't have a buffer object as data | |
| 45 | 0 | if(!(Buffer.isBuffer(data))) return callback(new Error("Data object must be a buffer object"), null); |
| 46 | // Get filename if we are using it | |
| 47 | 0 | var filename = options['filename'] || null; |
| 48 | // Get id if we are using it | |
| 49 | 0 | var id = options['_id'] || null; |
| 50 | // Create gridstore | |
| 51 | 0 | var gridStore = new GridStore(this.db, id, filename, "w", options); |
| 52 | 0 | gridStore.open(function(err, gridStore) { |
| 53 | 0 | if(err) return callback(err, null); |
| 54 | ||
| 55 | 0 | gridStore.write(data, function(err, result) { |
| 56 | 0 | if(err) return callback(err, null); |
| 57 | ||
| 58 | 0 | gridStore.close(function(err, result) { |
| 59 | 0 | if(err) return callback(err, null); |
| 60 | 0 | callback(null, result); |
| 61 | }) | |
| 62 | }) | |
| 63 | }) | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Get binary data to the grid | |
| 68 | * | |
| 69 | * @param {Any} id for file. | |
| 70 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 71 | * @return {null} | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | 1 | Grid.prototype.get = function(id, callback) { |
| 75 | // Create gridstore | |
| 76 | 0 | var gridStore = new GridStore(this.db, id, null, "r", {root:this.fsName}); |
| 77 | 0 | gridStore.open(function(err, gridStore) { |
| 78 | 0 | if(err) return callback(err, null); |
| 79 | ||
| 80 | // Return the data | |
| 81 | 0 | gridStore.read(function(err, data) { |
| 82 | 0 | return callback(err, data) |
| 83 | }); | |
| 84 | }) | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Delete file from grid | |
| 89 | * | |
| 90 | * @param {Any} id for file. | |
| 91 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 92 | * @return {null} | |
| 93 | * @api public | |
| 94 | */ | |
| 95 | 1 | Grid.prototype.delete = function(id, callback) { |
| 96 | // Create gridstore | |
| 97 | 0 | GridStore.unlink(this.db, id, {root:this.fsName}, function(err, result) { |
| 98 | 0 | if(err) return callback(err, false); |
| 99 | 0 | return callback(null, true); |
| 100 | }); | |
| 101 | } | |
| 102 | ||
| 103 | 1 | exports.Grid = Grid; |
| 104 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * @fileOverview GridFS is a tool for MongoDB to store files to the database. | |
| 3 | * Because of the restrictions of the object size the database can hold, a | |
| 4 | * facility to split a file into several chunks is needed. The {@link GridStore} | |
| 5 | * class offers a simplified api to interact with files while managing the | |
| 6 | * chunks of split files behind the scenes. More information about GridFS can be | |
| 7 | * found <a href="http://www.mongodb.org/display/DOCS/GridFS">here</a>. | |
| 8 | */ | |
| 9 | 1 | var Chunk = require('./chunk').Chunk, |
| 10 | DbCommand = require('../commands/db_command').DbCommand, | |
| 11 | ObjectID = require('bson').ObjectID, | |
| 12 | Buffer = require('buffer').Buffer, | |
| 13 | fs = require('fs'), | |
| 14 | timers = require('timers'), | |
| 15 | util = require('util'), | |
| 16 | inherits = util.inherits, | |
| 17 | ReadStream = require('./readstream').ReadStream, | |
| 18 | Stream = require('stream'); | |
| 19 | ||
| 20 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 21 | 1 | var processor = require('../utils').processor(); |
| 22 | ||
| 23 | 1 | var REFERENCE_BY_FILENAME = 0, |
| 24 | REFERENCE_BY_ID = 1; | |
| 25 | ||
| 26 | /** | |
| 27 | * A class representation of a file stored in GridFS. | |
| 28 | * | |
| 29 | * Modes | |
| 30 | * - **"r"** - read only. This is the default mode. | |
| 31 | * - **"w"** - write in truncate mode. Existing data will be overwriten. | |
| 32 | * - **w+"** - write in edit mode. | |
| 33 | * | |
| 34 | * Options | |
| 35 | * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 36 | * - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. | |
| 37 | * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. | |
| 38 | * - **metadata** {Object}, arbitrary data the user wants to store. | |
| 39 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 40 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 41 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 42 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 43 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 44 | * | |
| 45 | * @class Represents the GridStore. | |
| 46 | * @param {Db} db A database instance to interact with. | |
| 47 | * @param {Any} [id] optional unique id for this file | |
| 48 | * @param {String} [filename] optional filename for this file, no unique constrain on the field | |
| 49 | * @param {String} mode set the mode for this file. | |
| 50 | * @param {Object} options optional properties to specify. | |
| 51 | * @return {GridStore} | |
| 52 | */ | |
| 53 | 1 | var GridStore = function GridStore(db, id, filename, mode, options) { |
| 54 | 0 | if(!(this instanceof GridStore)) return new GridStore(db, id, filename, mode, options); |
| 55 | ||
| 56 | 0 | var self = this; |
| 57 | 0 | this.db = db; |
| 58 | ||
| 59 | // Call stream constructor | |
| 60 | 0 | if(typeof Stream == 'function') { |
| 61 | 0 | Stream.call(this); |
| 62 | } else { | |
| 63 | // 0.4.X backward compatibility fix | |
| 64 | 0 | Stream.Stream.call(this); |
| 65 | } | |
| 66 | ||
| 67 | // Handle options | |
| 68 | 0 | if(typeof options === 'undefined') options = {}; |
| 69 | // Handle mode | |
| 70 | 0 | if(typeof mode === 'undefined') { |
| 71 | 0 | mode = filename; |
| 72 | 0 | filename = undefined; |
| 73 | 0 | } else if(typeof mode == 'object') { |
| 74 | 0 | options = mode; |
| 75 | 0 | mode = filename; |
| 76 | 0 | filename = undefined; |
| 77 | } | |
| 78 | ||
| 79 | 0 | if(id instanceof ObjectID) { |
| 80 | 0 | this.referenceBy = REFERENCE_BY_ID; |
| 81 | 0 | this.fileId = id; |
| 82 | 0 | this.filename = filename; |
| 83 | 0 | } else if(typeof filename == 'undefined') { |
| 84 | 0 | this.referenceBy = REFERENCE_BY_FILENAME; |
| 85 | 0 | this.filename = id; |
| 86 | 0 | if (mode.indexOf('w') != null) { |
| 87 | 0 | this.fileId = new ObjectID(); |
| 88 | } | |
| 89 | } else { | |
| 90 | 0 | this.referenceBy = REFERENCE_BY_ID; |
| 91 | 0 | this.fileId = id; |
| 92 | 0 | this.filename = filename; |
| 93 | } | |
| 94 | ||
| 95 | // Set up the rest | |
| 96 | 0 | this.mode = mode == null ? "r" : mode; |
| 97 | 0 | this.options = options == null ? {w:1} : options; |
| 98 | ||
| 99 | // If we have no write concerns set w:1 as default | |
| 100 | 0 | if(this.options.w == null |
| 101 | && this.options.j == null | |
| 102 | 0 | && this.options.fsync == null) this.options.w = 1; |
| 103 | ||
| 104 | // Set the root if overridden | |
| 105 | 0 | this.root = this.options['root'] == null ? exports.GridStore.DEFAULT_ROOT_COLLECTION : this.options['root']; |
| 106 | 0 | this.position = 0; |
| 107 | 0 | this.readPreference = this.options.readPreference || 'primary'; |
| 108 | 0 | this.writeConcern = _getWriteConcern(this, this.options); |
| 109 | // Set default chunk size | |
| 110 | 0 | this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize']; |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Code for the streaming capabilities of the gridstore object | |
| 115 | * Most code from Aaron heckmanns project https://github.com/aheckmann/gridfs-stream | |
| 116 | * Modified to work on the gridstore object itself | |
| 117 | * @ignore | |
| 118 | */ | |
| 119 | 1 | if(typeof Stream == 'function') { |
| 120 | 1 | GridStore.prototype = { __proto__: Stream.prototype } |
| 121 | } else { | |
| 122 | // Node 0.4.X compatibility code | |
| 123 | 0 | GridStore.prototype = { __proto__: Stream.Stream.prototype } |
| 124 | } | |
| 125 | ||
| 126 | // Move pipe to _pipe | |
| 127 | 1 | GridStore.prototype._pipe = GridStore.prototype.pipe; |
| 128 | ||
| 129 | /** | |
| 130 | * Opens the file from the database and initialize this object. Also creates a | |
| 131 | * new one if file does not exist. | |
| 132 | * | |
| 133 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an **{Error}** object and the second parameter will be null if an error occured. Otherwise, the first parameter will be null and the second will contain the reference to this object. | |
| 134 | * @return {null} | |
| 135 | * @api public | |
| 136 | */ | |
| 137 | 1 | GridStore.prototype.open = function(callback) { |
| 138 | 0 | if( this.mode != "w" && this.mode != "w+" && this.mode != "r"){ |
| 139 | 0 | callback(new Error("Illegal mode " + this.mode), null); |
| 140 | 0 | return; |
| 141 | } | |
| 142 | ||
| 143 | 0 | var self = this; |
| 144 | // If we are writing we need to ensure we have the right indexes for md5's | |
| 145 | 0 | if((self.mode == "w" || self.mode == "w+")) { |
| 146 | // Get files collection | |
| 147 | 0 | self.collection(function(err, collection) { |
| 148 | 0 | if(err) return callback(err); |
| 149 | ||
| 150 | // Put index on filename | |
| 151 | 0 | collection.ensureIndex([['filename', 1]], function(err, index) { |
| 152 | 0 | if(err) return callback(err); |
| 153 | ||
| 154 | // Get chunk collection | |
| 155 | 0 | self.chunkCollection(function(err, chunkCollection) { |
| 156 | 0 | if(err) return callback(err); |
| 157 | ||
| 158 | // Ensure index on chunk collection | |
| 159 | 0 | chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { |
| 160 | 0 | if(err) return callback(err); |
| 161 | 0 | _open(self, callback); |
| 162 | }); | |
| 163 | }); | |
| 164 | }); | |
| 165 | }); | |
| 166 | } else { | |
| 167 | // Open the gridstore | |
| 168 | 0 | _open(self, callback); |
| 169 | } | |
| 170 | }; | |
| 171 | ||
| 172 | /** | |
| 173 | * Hidding the _open function | |
| 174 | * @ignore | |
| 175 | * @api private | |
| 176 | */ | |
| 177 | 1 | var _open = function(self, callback) { |
| 178 | 0 | self.collection(function(err, collection) { |
| 179 | 0 | if(err!==null) { |
| 180 | 0 | callback(new Error("at collection: "+err), null); |
| 181 | 0 | return; |
| 182 | } | |
| 183 | ||
| 184 | // Create the query | |
| 185 | 0 | var query = self.referenceBy == REFERENCE_BY_ID ? {_id:self.fileId} : {filename:self.filename}; |
| 186 | 0 | query = null == self.fileId && this.filename == null ? null : query; |
| 187 | ||
| 188 | // Fetch the chunks | |
| 189 | 0 | if(query != null) { |
| 190 | 0 | collection.find(query, {readPreference:self.readPreference}, function(err, cursor) { |
| 191 | 0 | if(err) return error(err); |
| 192 | ||
| 193 | // Fetch the file | |
| 194 | 0 | cursor.nextObject(function(err, doc) { |
| 195 | 0 | if(err) return error(err); |
| 196 | ||
| 197 | // Check if the collection for the files exists otherwise prepare the new one | |
| 198 | 0 | if(doc != null) { |
| 199 | 0 | self.fileId = doc._id; |
| 200 | 0 | self.filename = doc.filename; |
| 201 | 0 | self.contentType = doc.contentType; |
| 202 | 0 | self.internalChunkSize = doc.chunkSize; |
| 203 | 0 | self.uploadDate = doc.uploadDate; |
| 204 | 0 | self.aliases = doc.aliases; |
| 205 | 0 | self.length = doc.length; |
| 206 | 0 | self.metadata = doc.metadata; |
| 207 | 0 | self.internalMd5 = doc.md5; |
| 208 | 0 | } else if (self.mode != 'r') { |
| 209 | 0 | self.fileId = self.fileId == null ? new ObjectID() : self.fileId; |
| 210 | 0 | self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; |
| 211 | 0 | self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; |
| 212 | 0 | self.length = 0; |
| 213 | } else { | |
| 214 | 0 | self.length = 0; |
| 215 | 0 | var txtId = self.fileId instanceof ObjectID ? self.fileId.toHexString() : self.fileId; |
| 216 | 0 | return error(new Error((self.referenceBy == REFERENCE_BY_ID ? txtId : self.filename) + " does not exist", self)); |
| 217 | } | |
| 218 | ||
| 219 | // Process the mode of the object | |
| 220 | 0 | if(self.mode == "r") { |
| 221 | 0 | nthChunk(self, 0, function(err, chunk) { |
| 222 | 0 | if(err) return error(err); |
| 223 | 0 | self.currentChunk = chunk; |
| 224 | 0 | self.position = 0; |
| 225 | 0 | callback(null, self); |
| 226 | }); | |
| 227 | 0 | } else if(self.mode == "w") { |
| 228 | // Delete any existing chunks | |
| 229 | 0 | deleteChunks(self, function(err, result) { |
| 230 | 0 | if(err) return error(err); |
| 231 | 0 | self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern); |
| 232 | 0 | self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; |
| 233 | 0 | self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; |
| 234 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 235 | 0 | self.position = 0; |
| 236 | 0 | callback(null, self); |
| 237 | }); | |
| 238 | 0 | } else if(self.mode == "w+") { |
| 239 | 0 | nthChunk(self, lastChunkNumber(self), function(err, chunk) { |
| 240 | 0 | if(err) return error(err); |
| 241 | // Set the current chunk | |
| 242 | 0 | self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk; |
| 243 | 0 | self.currentChunk.position = self.currentChunk.data.length(); |
| 244 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 245 | 0 | self.position = self.length; |
| 246 | 0 | callback(null, self); |
| 247 | }); | |
| 248 | } | |
| 249 | }); | |
| 250 | }); | |
| 251 | } else { | |
| 252 | // Write only mode | |
| 253 | 0 | self.fileId = null == self.fileId ? new ObjectID() : self.fileId; |
| 254 | 0 | self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; |
| 255 | 0 | self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; |
| 256 | 0 | self.length = 0; |
| 257 | ||
| 258 | 0 | self.chunkCollection(function(err, collection2) { |
| 259 | 0 | if(err) return error(err); |
| 260 | ||
| 261 | // No file exists set up write mode | |
| 262 | 0 | if(self.mode == "w") { |
| 263 | // Delete any existing chunks | |
| 264 | 0 | deleteChunks(self, function(err, result) { |
| 265 | 0 | if(err) return error(err); |
| 266 | 0 | self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern); |
| 267 | 0 | self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; |
| 268 | 0 | self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; |
| 269 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 270 | 0 | self.position = 0; |
| 271 | 0 | callback(null, self); |
| 272 | }); | |
| 273 | 0 | } else if(self.mode == "w+") { |
| 274 | 0 | nthChunk(self, lastChunkNumber(self), function(err, chunk) { |
| 275 | 0 | if(err) return error(err); |
| 276 | // Set the current chunk | |
| 277 | 0 | self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk; |
| 278 | 0 | self.currentChunk.position = self.currentChunk.data.length(); |
| 279 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 280 | 0 | self.position = self.length; |
| 281 | 0 | callback(null, self); |
| 282 | }); | |
| 283 | } | |
| 284 | }); | |
| 285 | } | |
| 286 | }); | |
| 287 | ||
| 288 | // only pass error to callback once | |
| 289 | 0 | function error (err) { |
| 290 | 0 | if(error.err) return; |
| 291 | 0 | callback(error.err = err); |
| 292 | } | |
| 293 | }; | |
| 294 | ||
| 295 | /** | |
| 296 | * Stores a file from the file system to the GridFS database. | |
| 297 | * | |
| 298 | * @param {String|Buffer|FileHandle} file the file to store. | |
| 299 | * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the the second will contain the reference to this object. | |
| 300 | * @return {null} | |
| 301 | * @api public | |
| 302 | */ | |
| 303 | 1 | GridStore.prototype.writeFile = function (file, callback) { |
| 304 | 0 | var self = this; |
| 305 | 0 | if (typeof file === 'string') { |
| 306 | 0 | fs.open(file, 'r', function (err, fd) { |
| 307 | 0 | if(err) return callback(err); |
| 308 | 0 | self.writeFile(fd, callback); |
| 309 | }); | |
| 310 | 0 | return; |
| 311 | } | |
| 312 | ||
| 313 | 0 | self.open(function (err, self) { |
| 314 | 0 | if(err) return callback(err, self); |
| 315 | ||
| 316 | 0 | fs.fstat(file, function (err, stats) { |
| 317 | 0 | if(err) return callback(err, self); |
| 318 | ||
| 319 | 0 | var offset = 0; |
| 320 | 0 | var index = 0; |
| 321 | 0 | var numberOfChunksLeft = Math.min(stats.size / self.chunkSize); |
| 322 | ||
| 323 | // Write a chunk | |
| 324 | 0 | var writeChunk = function() { |
| 325 | 0 | fs.read(file, self.chunkSize, offset, 'binary', function(err, data, bytesRead) { |
| 326 | 0 | if(err) return callback(err, self); |
| 327 | ||
| 328 | 0 | offset = offset + bytesRead; |
| 329 | ||
| 330 | // Create a new chunk for the data | |
| 331 | 0 | var chunk = new Chunk(self, {n:index++}, self.writeConcern); |
| 332 | 0 | chunk.write(data, function(err, chunk) { |
| 333 | 0 | if(err) return callback(err, self); |
| 334 | ||
| 335 | 0 | chunk.save(function(err, result) { |
| 336 | 0 | if(err) return callback(err, self); |
| 337 | ||
| 338 | 0 | self.position = self.position + data.length; |
| 339 | ||
| 340 | // Point to current chunk | |
| 341 | 0 | self.currentChunk = chunk; |
| 342 | ||
| 343 | 0 | if(offset >= stats.size) { |
| 344 | 0 | fs.close(file); |
| 345 | 0 | self.close(function(err, result) { |
| 346 | 0 | if(err) return callback(err, self); |
| 347 | 0 | return callback(null, self); |
| 348 | }); | |
| 349 | } else { | |
| 350 | 0 | return processor(writeChunk); |
| 351 | } | |
| 352 | }); | |
| 353 | }); | |
| 354 | }); | |
| 355 | } | |
| 356 | ||
| 357 | // Process the first write | |
| 358 | 0 | processor(writeChunk); |
| 359 | }); | |
| 360 | }); | |
| 361 | }; | |
| 362 | ||
| 363 | /** | |
| 364 | * Writes some data. This method will work properly only if initialized with mode | |
| 365 | * "w" or "w+". | |
| 366 | * | |
| 367 | * @param string {string} The data to write. | |
| 368 | * @param close {boolean=false} opt_argument Closes this file after writing if | |
| 369 | * true. | |
| 370 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 371 | * this method. The first parameter will contain null and the second one | |
| 372 | * will contain a reference to this object. | |
| 373 | * | |
| 374 | * @ignore | |
| 375 | * @api private | |
| 376 | */ | |
| 377 | 1 | var writeBuffer = function(self, buffer, close, callback) { |
| 378 | 0 | if(typeof close === "function") { callback = close; close = null; } |
| 379 | 0 | var finalClose = (close == null) ? false : close; |
| 380 | ||
| 381 | 0 | if(self.mode[0] != "w") { |
| 382 | 0 | callback(new Error((self.referenceBy == REFERENCE_BY_ID ? self.toHexString() : self.filename) + " not opened for writing"), null); |
| 383 | } else { | |
| 384 | 0 | if(self.currentChunk.position + buffer.length >= self.chunkSize) { |
| 385 | // Write out the current Chunk and then keep writing until we have less data left than a chunkSize left | |
| 386 | // to a new chunk (recursively) | |
| 387 | 0 | var previousChunkNumber = self.currentChunk.chunkNumber; |
| 388 | 0 | var leftOverDataSize = self.chunkSize - self.currentChunk.position; |
| 389 | 0 | var firstChunkData = buffer.slice(0, leftOverDataSize); |
| 390 | 0 | var leftOverData = buffer.slice(leftOverDataSize); |
| 391 | // A list of chunks to write out | |
| 392 | 0 | var chunksToWrite = [self.currentChunk.write(firstChunkData)]; |
| 393 | // If we have more data left than the chunk size let's keep writing new chunks | |
| 394 | 0 | while(leftOverData.length >= self.chunkSize) { |
| 395 | // Create a new chunk and write to it | |
| 396 | 0 | var newChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern); |
| 397 | 0 | var firstChunkData = leftOverData.slice(0, self.chunkSize); |
| 398 | 0 | leftOverData = leftOverData.slice(self.chunkSize); |
| 399 | // Update chunk number | |
| 400 | 0 | previousChunkNumber = previousChunkNumber + 1; |
| 401 | // Write data | |
| 402 | 0 | newChunk.write(firstChunkData); |
| 403 | // Push chunk to save list | |
| 404 | 0 | chunksToWrite.push(newChunk); |
| 405 | } | |
| 406 | ||
| 407 | // Set current chunk with remaining data | |
| 408 | 0 | self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern); |
| 409 | // If we have left over data write it | |
| 410 | 0 | if(leftOverData.length > 0) self.currentChunk.write(leftOverData); |
| 411 | ||
| 412 | // Update the position for the gridstore | |
| 413 | 0 | self.position = self.position + buffer.length; |
| 414 | // Total number of chunks to write | |
| 415 | 0 | var numberOfChunksToWrite = chunksToWrite.length; |
| 416 | // Write out all the chunks and then return | |
| 417 | 0 | for(var i = 0; i < chunksToWrite.length; i++) { |
| 418 | 0 | var chunk = chunksToWrite[i]; |
| 419 | 0 | chunk.save(function(err, result) { |
| 420 | 0 | if(err) return callback(err); |
| 421 | ||
| 422 | 0 | numberOfChunksToWrite = numberOfChunksToWrite - 1; |
| 423 | ||
| 424 | 0 | if(numberOfChunksToWrite <= 0) { |
| 425 | 0 | return callback(null, self); |
| 426 | } | |
| 427 | }) | |
| 428 | } | |
| 429 | } else { | |
| 430 | // Update the position for the gridstore | |
| 431 | 0 | self.position = self.position + buffer.length; |
| 432 | // We have less data than the chunk size just write it and callback | |
| 433 | 0 | self.currentChunk.write(buffer); |
| 434 | 0 | callback(null, self); |
| 435 | } | |
| 436 | } | |
| 437 | }; | |
| 438 | ||
| 439 | /** | |
| 440 | * Creates a mongoDB object representation of this object. | |
| 441 | * | |
| 442 | * @param callback {function(object)} This will be called after executing this | |
| 443 | * method. The object will be passed to the first parameter and will have | |
| 444 | * the structure: | |
| 445 | * | |
| 446 | * <pre><code> | |
| 447 | * { | |
| 448 | * '_id' : , // {number} id for this file | |
| 449 | * 'filename' : , // {string} name for this file | |
| 450 | * 'contentType' : , // {string} mime type for this file | |
| 451 | * 'length' : , // {number} size of this file? | |
| 452 | * 'chunksize' : , // {number} chunk size used by this file | |
| 453 | * 'uploadDate' : , // {Date} | |
| 454 | * 'aliases' : , // {array of string} | |
| 455 | * 'metadata' : , // {string} | |
| 456 | * } | |
| 457 | * </code></pre> | |
| 458 | * | |
| 459 | * @ignore | |
| 460 | * @api private | |
| 461 | */ | |
| 462 | 1 | var buildMongoObject = function(self, callback) { |
| 463 | // Calcuate the length | |
| 464 | 0 | var mongoObject = { |
| 465 | '_id': self.fileId, | |
| 466 | 'filename': self.filename, | |
| 467 | 'contentType': self.contentType, | |
| 468 | 'length': self.position ? self.position : 0, | |
| 469 | 'chunkSize': self.chunkSize, | |
| 470 | 'uploadDate': self.uploadDate, | |
| 471 | 'aliases': self.aliases, | |
| 472 | 'metadata': self.metadata | |
| 473 | }; | |
| 474 | ||
| 475 | 0 | var md5Command = {filemd5:self.fileId, root:self.root}; |
| 476 | 0 | self.db.command(md5Command, function(err, results) { |
| 477 | 0 | mongoObject.md5 = results.md5; |
| 478 | 0 | callback(mongoObject); |
| 479 | }); | |
| 480 | }; | |
| 481 | ||
| 482 | /** | |
| 483 | * Saves this file to the database. This will overwrite the old entry if it | |
| 484 | * already exists. This will work properly only if mode was initialized to | |
| 485 | * "w" or "w+". | |
| 486 | * | |
| 487 | * @param {Function} callback this will be called after executing this method. Passes an **{Error}** object to the first parameter and null to the second if an error occured. Otherwise, passes null to the first and a reference to this object to the second. | |
| 488 | * @return {null} | |
| 489 | * @api public | |
| 490 | */ | |
| 491 | 1 | GridStore.prototype.close = function(callback) { |
| 492 | 0 | var self = this; |
| 493 | ||
| 494 | 0 | if(self.mode[0] == "w") { |
| 495 | 0 | if(self.currentChunk != null && self.currentChunk.position > 0) { |
| 496 | 0 | self.currentChunk.save(function(err, chunk) { |
| 497 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 498 | ||
| 499 | 0 | self.collection(function(err, files) { |
| 500 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 501 | ||
| 502 | // Build the mongo object | |
| 503 | 0 | if(self.uploadDate != null) { |
| 504 | 0 | files.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { |
| 505 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 506 | ||
| 507 | 0 | buildMongoObject(self, function(mongoObject) { |
| 508 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 509 | 0 | if(typeof callback == 'function') |
| 510 | 0 | callback(err, mongoObject); |
| 511 | }); | |
| 512 | }); | |
| 513 | }); | |
| 514 | } else { | |
| 515 | 0 | self.uploadDate = new Date(); |
| 516 | 0 | buildMongoObject(self, function(mongoObject) { |
| 517 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 518 | 0 | if(typeof callback == 'function') |
| 519 | 0 | callback(err, mongoObject); |
| 520 | }); | |
| 521 | }); | |
| 522 | } | |
| 523 | }); | |
| 524 | }); | |
| 525 | } else { | |
| 526 | 0 | self.collection(function(err, files) { |
| 527 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 528 | ||
| 529 | 0 | self.uploadDate = new Date(); |
| 530 | 0 | buildMongoObject(self, function(mongoObject) { |
| 531 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 532 | 0 | if(typeof callback == 'function') |
| 533 | 0 | callback(err, mongoObject); |
| 534 | }); | |
| 535 | }); | |
| 536 | }); | |
| 537 | } | |
| 538 | 0 | } else if(self.mode[0] == "r") { |
| 539 | 0 | if(typeof callback == 'function') |
| 540 | 0 | callback(null, null); |
| 541 | } else { | |
| 542 | 0 | if(typeof callback == 'function') |
| 543 | 0 | callback(new Error("Illegal mode " + self.mode), null); |
| 544 | } | |
| 545 | }; | |
| 546 | ||
| 547 | /** | |
| 548 | * Gets the nth chunk of this file. | |
| 549 | * | |
| 550 | * @param chunkNumber {number} The nth chunk to retrieve. | |
| 551 | * @param callback {function(*, Chunk|object)} This will be called after | |
| 552 | * executing this method. null will be passed to the first parameter while | |
| 553 | * a new {@link Chunk} instance will be passed to the second parameter if | |
| 554 | * the chunk was found or an empty object {} if not. | |
| 555 | * | |
| 556 | * @ignore | |
| 557 | * @api private | |
| 558 | */ | |
| 559 | 1 | var nthChunk = function(self, chunkNumber, callback) { |
| 560 | 0 | self.chunkCollection(function(err, collection) { |
| 561 | 0 | if(err) return callback(err); |
| 562 | ||
| 563 | 0 | collection.find({'files_id':self.fileId, 'n':chunkNumber}, {readPreference: self.readPreference}, function(err, cursor) { |
| 564 | 0 | if(err) return callback(err); |
| 565 | ||
| 566 | 0 | cursor.nextObject(function(err, chunk) { |
| 567 | 0 | if(err) return callback(err); |
| 568 | ||
| 569 | 0 | var finalChunk = chunk == null ? {} : chunk; |
| 570 | 0 | callback(null, new Chunk(self, finalChunk, self.writeConcern)); |
| 571 | }); | |
| 572 | }); | |
| 573 | }); | |
| 574 | }; | |
| 575 | ||
| 576 | /** | |
| 577 | * | |
| 578 | * @ignore | |
| 579 | * @api private | |
| 580 | */ | |
| 581 | 1 | GridStore.prototype._nthChunk = function(chunkNumber, callback) { |
| 582 | 0 | nthChunk(this, chunkNumber, callback); |
| 583 | } | |
| 584 | ||
| 585 | /** | |
| 586 | * @return {Number} The last chunk number of this file. | |
| 587 | * | |
| 588 | * @ignore | |
| 589 | * @api private | |
| 590 | */ | |
| 591 | 1 | var lastChunkNumber = function(self) { |
| 592 | 0 | return Math.floor(self.length/self.chunkSize); |
| 593 | }; | |
| 594 | ||
| 595 | /** | |
| 596 | * Retrieve this file's chunks collection. | |
| 597 | * | |
| 598 | * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. | |
| 599 | * @return {null} | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | 1 | GridStore.prototype.chunkCollection = function(callback) { |
| 603 | 0 | this.db.collection((this.root + ".chunks"), callback); |
| 604 | }; | |
| 605 | ||
| 606 | /** | |
| 607 | * Deletes all the chunks of this file in the database. | |
| 608 | * | |
| 609 | * @param callback {function(*, boolean)} This will be called after this method | |
| 610 | * executes. Passes null to the first and true to the second argument. | |
| 611 | * | |
| 612 | * @ignore | |
| 613 | * @api private | |
| 614 | */ | |
| 615 | 1 | var deleteChunks = function(self, callback) { |
| 616 | 0 | if(self.fileId != null) { |
| 617 | 0 | self.chunkCollection(function(err, collection) { |
| 618 | 0 | if(err) return callback(err, false); |
| 619 | 0 | collection.remove({'files_id':self.fileId}, {safe:true}, function(err, result) { |
| 620 | 0 | if(err) return callback(err, false); |
| 621 | 0 | callback(null, true); |
| 622 | }); | |
| 623 | }); | |
| 624 | } else { | |
| 625 | 0 | callback(null, true); |
| 626 | } | |
| 627 | }; | |
| 628 | ||
| 629 | /** | |
| 630 | * Deletes all the chunks of this file in the database. | |
| 631 | * | |
| 632 | * @param {Function} callback this will be called after this method executes. Passes null to the first and true to the second argument. | |
| 633 | * @return {null} | |
| 634 | * @api public | |
| 635 | */ | |
| 636 | 1 | GridStore.prototype.unlink = function(callback) { |
| 637 | 0 | var self = this; |
| 638 | 0 | deleteChunks(this, function(err) { |
| 639 | 0 | if(err!==null) { |
| 640 | 0 | err.message = "at deleteChunks: " + err.message; |
| 641 | 0 | return callback(err); |
| 642 | } | |
| 643 | ||
| 644 | 0 | self.collection(function(err, collection) { |
| 645 | 0 | if(err!==null) { |
| 646 | 0 | err.message = "at collection: " + err.message; |
| 647 | 0 | return callback(err); |
| 648 | } | |
| 649 | ||
| 650 | 0 | collection.remove({'_id':self.fileId}, {safe:true}, function(err) { |
| 651 | 0 | callback(err, self); |
| 652 | }); | |
| 653 | }); | |
| 654 | }); | |
| 655 | }; | |
| 656 | ||
| 657 | /** | |
| 658 | * Retrieves the file collection associated with this object. | |
| 659 | * | |
| 660 | * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. | |
| 661 | * @return {null} | |
| 662 | * @api public | |
| 663 | */ | |
| 664 | 1 | GridStore.prototype.collection = function(callback) { |
| 665 | 0 | this.db.collection(this.root + ".files", callback); |
| 666 | }; | |
| 667 | ||
| 668 | /** | |
| 669 | * Reads the data of this file. | |
| 670 | * | |
| 671 | * @param {String} [separator] the character to be recognized as the newline separator. | |
| 672 | * @param {Function} callback This will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. | |
| 673 | * @return {null} | |
| 674 | * @api public | |
| 675 | */ | |
| 676 | 1 | GridStore.prototype.readlines = function(separator, callback) { |
| 677 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 678 | 0 | callback = args.pop(); |
| 679 | 0 | separator = args.length ? args.shift() : "\n"; |
| 680 | ||
| 681 | 0 | this.read(function(err, data) { |
| 682 | 0 | if(err) return callback(err); |
| 683 | ||
| 684 | 0 | var items = data.toString().split(separator); |
| 685 | 0 | items = items.length > 0 ? items.splice(0, items.length - 1) : []; |
| 686 | 0 | for(var i = 0; i < items.length; i++) { |
| 687 | 0 | items[i] = items[i] + separator; |
| 688 | } | |
| 689 | ||
| 690 | 0 | callback(null, items); |
| 691 | }); | |
| 692 | }; | |
| 693 | ||
| 694 | /** | |
| 695 | * Deletes all the chunks of this file in the database if mode was set to "w" or | |
| 696 | * "w+" and resets the read/write head to the initial position. | |
| 697 | * | |
| 698 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 699 | * @return {null} | |
| 700 | * @api public | |
| 701 | */ | |
| 702 | 1 | GridStore.prototype.rewind = function(callback) { |
| 703 | 0 | var self = this; |
| 704 | ||
| 705 | 0 | if(this.currentChunk.chunkNumber != 0) { |
| 706 | 0 | if(this.mode[0] == "w") { |
| 707 | 0 | deleteChunks(self, function(err, gridStore) { |
| 708 | 0 | if(err) return callback(err); |
| 709 | 0 | self.currentChunk = new Chunk(self, {'n': 0}, self.writeConcern); |
| 710 | 0 | self.position = 0; |
| 711 | 0 | callback(null, self); |
| 712 | }); | |
| 713 | } else { | |
| 714 | 0 | self.currentChunk(0, function(err, chunk) { |
| 715 | 0 | if(err) return callback(err); |
| 716 | 0 | self.currentChunk = chunk; |
| 717 | 0 | self.currentChunk.rewind(); |
| 718 | 0 | self.position = 0; |
| 719 | 0 | callback(null, self); |
| 720 | }); | |
| 721 | } | |
| 722 | } else { | |
| 723 | 0 | self.currentChunk.rewind(); |
| 724 | 0 | self.position = 0; |
| 725 | 0 | callback(null, self); |
| 726 | } | |
| 727 | }; | |
| 728 | ||
| 729 | /** | |
| 730 | * Retrieves the contents of this file and advances the read/write head. Works with Buffers only. | |
| 731 | * | |
| 732 | * There are 3 signatures for this method: | |
| 733 | * | |
| 734 | * (callback) | |
| 735 | * (length, callback) | |
| 736 | * (length, buffer, callback) | |
| 737 | * | |
| 738 | * @param {Number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified. | |
| 739 | * @param {String|Buffer} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method. | |
| 740 | * @param {Function} callback this will be called after this method is executed. null will be passed to the first parameter and a string containing the contents of the buffer concatenated with the contents read from this file will be passed to the second. | |
| 741 | * @return {null} | |
| 742 | * @api public | |
| 743 | */ | |
| 744 | 1 | GridStore.prototype.read = function(length, buffer, callback) { |
| 745 | 0 | var self = this; |
| 746 | ||
| 747 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 748 | 0 | callback = args.pop(); |
| 749 | 0 | length = args.length ? args.shift() : null; |
| 750 | 0 | buffer = args.length ? args.shift() : null; |
| 751 | ||
| 752 | // The data is a c-terminated string and thus the length - 1 | |
| 753 | 0 | var finalLength = length == null ? self.length - self.position : length; |
| 754 | 0 | var finalBuffer = buffer == null ? new Buffer(finalLength) : buffer; |
| 755 | // Add a index to buffer to keep track of writing position or apply current index | |
| 756 | 0 | finalBuffer._index = buffer != null && buffer._index != null ? buffer._index : 0; |
| 757 | ||
| 758 | 0 | if((self.currentChunk.length() - self.currentChunk.position + finalBuffer._index) >= finalLength) { |
| 759 | 0 | var slice = self.currentChunk.readSlice(finalLength - finalBuffer._index); |
| 760 | // Copy content to final buffer | |
| 761 | 0 | slice.copy(finalBuffer, finalBuffer._index); |
| 762 | // Update internal position | |
| 763 | 0 | self.position = self.position + finalBuffer.length; |
| 764 | // Check if we don't have a file at all | |
| 765 | 0 | if(finalLength == 0 && finalBuffer.length == 0) return callback(new Error("File does not exist"), null); |
| 766 | // Else return data | |
| 767 | 0 | callback(null, finalBuffer); |
| 768 | } else { | |
| 769 | 0 | var slice = self.currentChunk.readSlice(self.currentChunk.length() - self.currentChunk.position); |
| 770 | // Copy content to final buffer | |
| 771 | 0 | slice.copy(finalBuffer, finalBuffer._index); |
| 772 | // Update index position | |
| 773 | 0 | finalBuffer._index += slice.length; |
| 774 | ||
| 775 | // Load next chunk and read more | |
| 776 | 0 | nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 777 | 0 | if(err) return callback(err); |
| 778 | ||
| 779 | 0 | if(chunk.length() > 0) { |
| 780 | 0 | self.currentChunk = chunk; |
| 781 | 0 | self.read(length, finalBuffer, callback); |
| 782 | } else { | |
| 783 | 0 | if (finalBuffer._index > 0) { |
| 784 | 0 | callback(null, finalBuffer) |
| 785 | } else { | |
| 786 | 0 | callback(new Error("no chunks found for file, possibly corrupt"), null); |
| 787 | } | |
| 788 | } | |
| 789 | }); | |
| 790 | } | |
| 791 | } | |
| 792 | ||
| 793 | /** | |
| 794 | * Retrieves the position of the read/write head of this file. | |
| 795 | * | |
| 796 | * @param {Function} callback This gets called after this method terminates. null is passed to the first parameter and the position is passed to the second. | |
| 797 | * @return {null} | |
| 798 | * @api public | |
| 799 | */ | |
| 800 | 1 | GridStore.prototype.tell = function(callback) { |
| 801 | 0 | callback(null, this.position); |
| 802 | }; | |
| 803 | ||
| 804 | /** | |
| 805 | * Moves the read/write head to a new location. | |
| 806 | * | |
| 807 | * There are 3 signatures for this method | |
| 808 | * | |
| 809 | * Seek Location Modes | |
| 810 | * - **GridStore.IO_SEEK_SET**, **(default)** set the position from the start of the file. | |
| 811 | * - **GridStore.IO_SEEK_CUR**, set the position from the current position in the file. | |
| 812 | * - **GridStore.IO_SEEK_END**, set the position from the end of the file. | |
| 813 | * | |
| 814 | * @param {Number} [position] the position to seek to | |
| 815 | * @param {Number} [seekLocation] seek mode. Use one of the Seek Location modes. | |
| 816 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 817 | * @return {null} | |
| 818 | * @api public | |
| 819 | */ | |
| 820 | 1 | GridStore.prototype.seek = function(position, seekLocation, callback) { |
| 821 | 0 | var self = this; |
| 822 | ||
| 823 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 824 | 0 | callback = args.pop(); |
| 825 | 0 | seekLocation = args.length ? args.shift() : null; |
| 826 | ||
| 827 | 0 | var seekLocationFinal = seekLocation == null ? exports.GridStore.IO_SEEK_SET : seekLocation; |
| 828 | 0 | var finalPosition = position; |
| 829 | 0 | var targetPosition = 0; |
| 830 | ||
| 831 | // Calculate the position | |
| 832 | 0 | if(seekLocationFinal == exports.GridStore.IO_SEEK_CUR) { |
| 833 | 0 | targetPosition = self.position + finalPosition; |
| 834 | 0 | } else if(seekLocationFinal == exports.GridStore.IO_SEEK_END) { |
| 835 | 0 | targetPosition = self.length + finalPosition; |
| 836 | } else { | |
| 837 | 0 | targetPosition = finalPosition; |
| 838 | } | |
| 839 | ||
| 840 | // Get the chunk | |
| 841 | 0 | var newChunkNumber = Math.floor(targetPosition/self.chunkSize); |
| 842 | 0 | if(newChunkNumber != self.currentChunk.chunkNumber) { |
| 843 | 0 | var seekChunk = function() { |
| 844 | 0 | nthChunk(self, newChunkNumber, function(err, chunk) { |
| 845 | 0 | self.currentChunk = chunk; |
| 846 | 0 | self.position = targetPosition; |
| 847 | 0 | self.currentChunk.position = (self.position % self.chunkSize); |
| 848 | 0 | callback(err, self); |
| 849 | }); | |
| 850 | }; | |
| 851 | ||
| 852 | 0 | if(self.mode[0] == 'w') { |
| 853 | 0 | self.currentChunk.save(function(err) { |
| 854 | 0 | if(err) return callback(err); |
| 855 | 0 | seekChunk(); |
| 856 | }); | |
| 857 | } else { | |
| 858 | 0 | seekChunk(); |
| 859 | } | |
| 860 | } else { | |
| 861 | 0 | self.position = targetPosition; |
| 862 | 0 | self.currentChunk.position = (self.position % self.chunkSize); |
| 863 | 0 | callback(null, self); |
| 864 | } | |
| 865 | }; | |
| 866 | ||
| 867 | /** | |
| 868 | * Verify if the file is at EOF. | |
| 869 | * | |
| 870 | * @return {Boolean} true if the read/write head is at the end of this file. | |
| 871 | * @api public | |
| 872 | */ | |
| 873 | 1 | GridStore.prototype.eof = function() { |
| 874 | 0 | return this.position == this.length ? true : false; |
| 875 | }; | |
| 876 | ||
| 877 | /** | |
| 878 | * Retrieves a single character from this file. | |
| 879 | * | |
| 880 | * @param {Function} callback this gets called after this method is executed. Passes null to the first parameter and the character read to the second or null to the second if the read/write head is at the end of the file. | |
| 881 | * @return {null} | |
| 882 | * @api public | |
| 883 | */ | |
| 884 | 1 | GridStore.prototype.getc = function(callback) { |
| 885 | 0 | var self = this; |
| 886 | ||
| 887 | 0 | if(self.eof()) { |
| 888 | 0 | callback(null, null); |
| 889 | 0 | } else if(self.currentChunk.eof()) { |
| 890 | 0 | nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 891 | 0 | self.currentChunk = chunk; |
| 892 | 0 | self.position = self.position + 1; |
| 893 | 0 | callback(err, self.currentChunk.getc()); |
| 894 | }); | |
| 895 | } else { | |
| 896 | 0 | self.position = self.position + 1; |
| 897 | 0 | callback(null, self.currentChunk.getc()); |
| 898 | } | |
| 899 | }; | |
| 900 | ||
| 901 | /** | |
| 902 | * Writes a string to the file with a newline character appended at the end if | |
| 903 | * the given string does not have one. | |
| 904 | * | |
| 905 | * @param {String} string the string to write. | |
| 906 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 907 | * @return {null} | |
| 908 | * @api public | |
| 909 | */ | |
| 910 | 1 | GridStore.prototype.puts = function(string, callback) { |
| 911 | 0 | var finalString = string.match(/\n$/) == null ? string + "\n" : string; |
| 912 | 0 | this.write(finalString, callback); |
| 913 | }; | |
| 914 | ||
| 915 | /** | |
| 916 | * Returns read stream based on this GridStore file | |
| 917 | * | |
| 918 | * Events | |
| 919 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 920 | * - **end** {function() {}} the end event triggers when there is no more documents available. | |
| 921 | * - **close** {function() {}} the close event triggers when the stream is closed. | |
| 922 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 923 | * | |
| 924 | * @param {Boolean} autoclose if true current GridStore will be closed when EOF and 'close' event will be fired | |
| 925 | * @return {null} | |
| 926 | * @api public | |
| 927 | */ | |
| 928 | 1 | GridStore.prototype.stream = function(autoclose) { |
| 929 | 0 | return new ReadStream(autoclose, this); |
| 930 | }; | |
| 931 | ||
| 932 | /** | |
| 933 | * The collection to be used for holding the files and chunks collection. | |
| 934 | * | |
| 935 | * @classconstant DEFAULT_ROOT_COLLECTION | |
| 936 | **/ | |
| 937 | 1 | GridStore.DEFAULT_ROOT_COLLECTION = 'fs'; |
| 938 | ||
| 939 | /** | |
| 940 | * Default file mime type | |
| 941 | * | |
| 942 | * @classconstant DEFAULT_CONTENT_TYPE | |
| 943 | **/ | |
| 944 | 1 | GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream'; |
| 945 | ||
| 946 | /** | |
| 947 | * Seek mode where the given length is absolute. | |
| 948 | * | |
| 949 | * @classconstant IO_SEEK_SET | |
| 950 | **/ | |
| 951 | 1 | GridStore.IO_SEEK_SET = 0; |
| 952 | ||
| 953 | /** | |
| 954 | * Seek mode where the given length is an offset to the current read/write head. | |
| 955 | * | |
| 956 | * @classconstant IO_SEEK_CUR | |
| 957 | **/ | |
| 958 | 1 | GridStore.IO_SEEK_CUR = 1; |
| 959 | ||
| 960 | /** | |
| 961 | * Seek mode where the given length is an offset to the end of the file. | |
| 962 | * | |
| 963 | * @classconstant IO_SEEK_END | |
| 964 | **/ | |
| 965 | 1 | GridStore.IO_SEEK_END = 2; |
| 966 | ||
| 967 | /** | |
| 968 | * Checks if a file exists in the database. | |
| 969 | * | |
| 970 | * Options | |
| 971 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 972 | * | |
| 973 | * @param {Db} db the database to query. | |
| 974 | * @param {String} name the name of the file to look for. | |
| 975 | * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 976 | * @param {Function} callback this will be called after this method executes. Passes null to the first and passes true to the second if the file exists and false otherwise. | |
| 977 | * @return {null} | |
| 978 | * @api public | |
| 979 | */ | |
| 980 | 1 | GridStore.exist = function(db, fileIdObject, rootCollection, options, callback) { |
| 981 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 982 | 0 | callback = args.pop(); |
| 983 | 0 | rootCollection = args.length ? args.shift() : null; |
| 984 | 0 | options = args.length ? args.shift() : {}; |
| 985 | ||
| 986 | // Establish read preference | |
| 987 | 0 | var readPreference = options.readPreference || 'primary'; |
| 988 | // Fetch collection | |
| 989 | 0 | var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; |
| 990 | 0 | db.collection(rootCollectionFinal + ".files", function(err, collection) { |
| 991 | 0 | if(err) return callback(err); |
| 992 | ||
| 993 | // Build query | |
| 994 | 0 | var query = (typeof fileIdObject == 'string' || Object.prototype.toString.call(fileIdObject) == '[object RegExp]' ) |
| 995 | ? {'filename':fileIdObject} | |
| 996 | : {'_id':fileIdObject}; // Attempt to locate file | |
| 997 | ||
| 998 | 0 | collection.find(query, {readPreference:readPreference}, function(err, cursor) { |
| 999 | 0 | if(err) return callback(err); |
| 1000 | ||
| 1001 | 0 | cursor.nextObject(function(err, item) { |
| 1002 | 0 | if(err) return callback(err); |
| 1003 | 0 | callback(null, item == null ? false : true); |
| 1004 | }); | |
| 1005 | }); | |
| 1006 | }); | |
| 1007 | }; | |
| 1008 | ||
| 1009 | /** | |
| 1010 | * Gets the list of files stored in the GridFS. | |
| 1011 | * | |
| 1012 | * @param {Db} db the database to query. | |
| 1013 | * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 1014 | * @param {Function} callback this will be called after this method executes. Passes null to the first and passes an array of strings containing the names of the files. | |
| 1015 | * @return {null} | |
| 1016 | * @api public | |
| 1017 | */ | |
| 1018 | 1 | GridStore.list = function(db, rootCollection, options, callback) { |
| 1019 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 1020 | 0 | callback = args.pop(); |
| 1021 | 0 | rootCollection = args.length ? args.shift() : null; |
| 1022 | 0 | options = args.length ? args.shift() : {}; |
| 1023 | ||
| 1024 | // Ensure we have correct values | |
| 1025 | 0 | if(rootCollection != null && typeof rootCollection == 'object') { |
| 1026 | 0 | options = rootCollection; |
| 1027 | 0 | rootCollection = null; |
| 1028 | } | |
| 1029 | ||
| 1030 | // Establish read preference | |
| 1031 | 0 | var readPreference = options.readPreference || 'primary'; |
| 1032 | // Check if we are returning by id not filename | |
| 1033 | 0 | var byId = options['id'] != null ? options['id'] : false; |
| 1034 | // Fetch item | |
| 1035 | 0 | var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; |
| 1036 | 0 | var items = []; |
| 1037 | 0 | db.collection((rootCollectionFinal + ".files"), function(err, collection) { |
| 1038 | 0 | if(err) return callback(err); |
| 1039 | ||
| 1040 | 0 | collection.find({}, {readPreference:readPreference}, function(err, cursor) { |
| 1041 | 0 | if(err) return callback(err); |
| 1042 | ||
| 1043 | 0 | cursor.each(function(err, item) { |
| 1044 | 0 | if(item != null) { |
| 1045 | 0 | items.push(byId ? item._id : item.filename); |
| 1046 | } else { | |
| 1047 | 0 | callback(err, items); |
| 1048 | } | |
| 1049 | }); | |
| 1050 | }); | |
| 1051 | }); | |
| 1052 | }; | |
| 1053 | ||
| 1054 | /** | |
| 1055 | * Reads the contents of a file. | |
| 1056 | * | |
| 1057 | * This method has the following signatures | |
| 1058 | * | |
| 1059 | * (db, name, callback) | |
| 1060 | * (db, name, length, callback) | |
| 1061 | * (db, name, length, offset, callback) | |
| 1062 | * (db, name, length, offset, options, callback) | |
| 1063 | * | |
| 1064 | * @param {Db} db the database to query. | |
| 1065 | * @param {String} name the name of the file. | |
| 1066 | * @param {Number} [length] the size of data to read. | |
| 1067 | * @param {Number} [offset] the offset from the head of the file of which to start reading from. | |
| 1068 | * @param {Object} [options] the options for the file. | |
| 1069 | * @param {Function} callback this will be called after this method executes. A string with an error message will be passed to the first parameter when the length and offset combination exceeds the length of the file while an Error object will be passed if other forms of error occured, otherwise, a string is passed. The second parameter will contain the data read if successful or null if an error occured. | |
| 1070 | * @return {null} | |
| 1071 | * @api public | |
| 1072 | */ | |
| 1073 | 1 | GridStore.read = function(db, name, length, offset, options, callback) { |
| 1074 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1075 | 0 | callback = args.pop(); |
| 1076 | 0 | length = args.length ? args.shift() : null; |
| 1077 | 0 | offset = args.length ? args.shift() : null; |
| 1078 | 0 | options = args.length ? args.shift() : null; |
| 1079 | ||
| 1080 | 0 | new GridStore(db, name, "r", options).open(function(err, gridStore) { |
| 1081 | 0 | if(err) return callback(err); |
| 1082 | // Make sure we are not reading out of bounds | |
| 1083 | 0 | if(offset && offset >= gridStore.length) return callback("offset larger than size of file", null); |
| 1084 | 0 | if(length && length > gridStore.length) return callback("length is larger than the size of the file", null); |
| 1085 | 0 | if(offset && length && (offset + length) > gridStore.length) return callback("offset and length is larger than the size of the file", null); |
| 1086 | ||
| 1087 | 0 | if(offset != null) { |
| 1088 | 0 | gridStore.seek(offset, function(err, gridStore) { |
| 1089 | 0 | if(err) return callback(err); |
| 1090 | 0 | gridStore.read(length, callback); |
| 1091 | }); | |
| 1092 | } else { | |
| 1093 | 0 | gridStore.read(length, callback); |
| 1094 | } | |
| 1095 | }); | |
| 1096 | }; | |
| 1097 | ||
| 1098 | /** | |
| 1099 | * Reads the data of this file. | |
| 1100 | * | |
| 1101 | * @param {Db} db the database to query. | |
| 1102 | * @param {String} name the name of the file. | |
| 1103 | * @param {String} [separator] the character to be recognized as the newline separator. | |
| 1104 | * @param {Object} [options] file options. | |
| 1105 | * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. | |
| 1106 | * @return {null} | |
| 1107 | * @api public | |
| 1108 | */ | |
| 1109 | 1 | GridStore.readlines = function(db, name, separator, options, callback) { |
| 1110 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1111 | 0 | callback = args.pop(); |
| 1112 | 0 | separator = args.length ? args.shift() : null; |
| 1113 | 0 | options = args.length ? args.shift() : null; |
| 1114 | ||
| 1115 | 0 | var finalSeperator = separator == null ? "\n" : separator; |
| 1116 | 0 | new GridStore(db, name, "r", options).open(function(err, gridStore) { |
| 1117 | 0 | if(err) return callback(err); |
| 1118 | 0 | gridStore.readlines(finalSeperator, callback); |
| 1119 | }); | |
| 1120 | }; | |
| 1121 | ||
| 1122 | /** | |
| 1123 | * Deletes the chunks and metadata information of a file from GridFS. | |
| 1124 | * | |
| 1125 | * @param {Db} db the database to interact with. | |
| 1126 | * @param {String|Array} names the name/names of the files to delete. | |
| 1127 | * @param {Object} [options] the options for the files. | |
| 1128 | * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 1129 | * @return {null} | |
| 1130 | * @api public | |
| 1131 | */ | |
| 1132 | 1 | GridStore.unlink = function(db, names, options, callback) { |
| 1133 | 0 | var self = this; |
| 1134 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1135 | 0 | callback = args.pop(); |
| 1136 | 0 | options = args.length ? args.shift() : null; |
| 1137 | ||
| 1138 | 0 | if(names.constructor == Array) { |
| 1139 | 0 | var tc = 0; |
| 1140 | 0 | for(var i = 0; i < names.length; i++) { |
| 1141 | 0 | ++tc; |
| 1142 | 0 | self.unlink(db, names[i], options, function(result) { |
| 1143 | 0 | if(--tc == 0) { |
| 1144 | 0 | callback(null, self); |
| 1145 | } | |
| 1146 | }); | |
| 1147 | } | |
| 1148 | } else { | |
| 1149 | 0 | new GridStore(db, names, "w", options).open(function(err, gridStore) { |
| 1150 | 0 | if(err) return callback(err); |
| 1151 | 0 | deleteChunks(gridStore, function(err, result) { |
| 1152 | 0 | if(err) return callback(err); |
| 1153 | 0 | gridStore.collection(function(err, collection) { |
| 1154 | 0 | if(err) return callback(err); |
| 1155 | 0 | collection.remove({'_id':gridStore.fileId}, {safe:true}, function(err, collection) { |
| 1156 | 0 | callback(err, self); |
| 1157 | }); | |
| 1158 | }); | |
| 1159 | }); | |
| 1160 | }); | |
| 1161 | } | |
| 1162 | }; | |
| 1163 | ||
| 1164 | /** | |
| 1165 | * Returns the current chunksize of the file. | |
| 1166 | * | |
| 1167 | * @field chunkSize | |
| 1168 | * @type {Number} | |
| 1169 | * @getter | |
| 1170 | * @setter | |
| 1171 | * @property return number of bytes in the current chunkSize. | |
| 1172 | */ | |
| 1173 | 1 | Object.defineProperty(GridStore.prototype, "chunkSize", { enumerable: true |
| 1174 | , get: function () { | |
| 1175 | 0 | return this.internalChunkSize; |
| 1176 | } | |
| 1177 | , set: function(value) { | |
| 1178 | 0 | if(!(this.mode[0] == "w" && this.position == 0 && this.uploadDate == null)) { |
| 1179 | 0 | this.internalChunkSize = this.internalChunkSize; |
| 1180 | } else { | |
| 1181 | 0 | this.internalChunkSize = value; |
| 1182 | } | |
| 1183 | } | |
| 1184 | }); | |
| 1185 | ||
| 1186 | /** | |
| 1187 | * The md5 checksum for this file. | |
| 1188 | * | |
| 1189 | * @field md5 | |
| 1190 | * @type {Number} | |
| 1191 | * @getter | |
| 1192 | * @setter | |
| 1193 | * @property return this files md5 checksum. | |
| 1194 | */ | |
| 1195 | 1 | Object.defineProperty(GridStore.prototype, "md5", { enumerable: true |
| 1196 | , get: function () { | |
| 1197 | 0 | return this.internalMd5; |
| 1198 | } | |
| 1199 | }); | |
| 1200 | ||
| 1201 | /** | |
| 1202 | * GridStore Streaming methods | |
| 1203 | * Handles the correct return of the writeable stream status | |
| 1204 | * @ignore | |
| 1205 | */ | |
| 1206 | 1 | Object.defineProperty(GridStore.prototype, "writable", { enumerable: true |
| 1207 | , get: function () { | |
| 1208 | 0 | if(this._writeable == null) { |
| 1209 | 0 | this._writeable = this.mode != null && this.mode.indexOf("w") != -1; |
| 1210 | } | |
| 1211 | // Return the _writeable | |
| 1212 | 0 | return this._writeable; |
| 1213 | } | |
| 1214 | , set: function(value) { | |
| 1215 | 0 | this._writeable = value; |
| 1216 | } | |
| 1217 | }); | |
| 1218 | ||
| 1219 | /** | |
| 1220 | * Handles the correct return of the readable stream status | |
| 1221 | * @ignore | |
| 1222 | */ | |
| 1223 | 1 | Object.defineProperty(GridStore.prototype, "readable", { enumerable: true |
| 1224 | , get: function () { | |
| 1225 | 0 | if(this._readable == null) { |
| 1226 | 0 | this._readable = this.mode != null && this.mode.indexOf("r") != -1; |
| 1227 | } | |
| 1228 | 0 | return this._readable; |
| 1229 | } | |
| 1230 | , set: function(value) { | |
| 1231 | 0 | this._readable = value; |
| 1232 | } | |
| 1233 | }); | |
| 1234 | ||
| 1235 | 1 | GridStore.prototype.paused; |
| 1236 | ||
| 1237 | /** | |
| 1238 | * Handles the correct setting of encoding for the stream | |
| 1239 | * @ignore | |
| 1240 | */ | |
| 1241 | 1 | GridStore.prototype.setEncoding = fs.ReadStream.prototype.setEncoding; |
| 1242 | ||
| 1243 | /** | |
| 1244 | * Handles the end events | |
| 1245 | * @ignore | |
| 1246 | */ | |
| 1247 | 1 | GridStore.prototype.end = function end(data) { |
| 1248 | 0 | var self = this; |
| 1249 | // allow queued data to write before closing | |
| 1250 | 0 | if(!this.writable) return; |
| 1251 | 0 | this.writable = false; |
| 1252 | ||
| 1253 | 0 | if(data) { |
| 1254 | 0 | this._q.push(data); |
| 1255 | } | |
| 1256 | ||
| 1257 | 0 | this.on('drain', function () { |
| 1258 | 0 | self.close(function (err) { |
| 1259 | 0 | if (err) return _error(self, err); |
| 1260 | 0 | self.emit('close'); |
| 1261 | }); | |
| 1262 | }); | |
| 1263 | ||
| 1264 | 0 | _flush(self); |
| 1265 | } | |
| 1266 | ||
| 1267 | /** | |
| 1268 | * Handles the normal writes to gridstore | |
| 1269 | * @ignore | |
| 1270 | */ | |
| 1271 | 1 | var _writeNormal = function(self, data, close, callback) { |
| 1272 | // If we have a buffer write it using the writeBuffer method | |
| 1273 | 0 | if(Buffer.isBuffer(data)) { |
| 1274 | 0 | return writeBuffer(self, data, close, callback); |
| 1275 | } else { | |
| 1276 | // Wrap the string in a buffer and write | |
| 1277 | 0 | return writeBuffer(self, new Buffer(data, 'binary'), close, callback); |
| 1278 | } | |
| 1279 | } | |
| 1280 | ||
| 1281 | /** | |
| 1282 | * Writes some data. This method will work properly only if initialized with mode "w" or "w+". | |
| 1283 | * | |
| 1284 | * @param {String|Buffer} data the data to write. | |
| 1285 | * @param {Boolean} [close] closes this file after writing if set to true. | |
| 1286 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 1287 | * @return {null} | |
| 1288 | * @api public | |
| 1289 | */ | |
| 1290 | 1 | GridStore.prototype.write = function write(data, close, callback) { |
| 1291 | // If it's a normal write delegate the call | |
| 1292 | 0 | if(typeof close == 'function' || typeof callback == 'function') { |
| 1293 | 0 | return _writeNormal(this, data, close, callback); |
| 1294 | } | |
| 1295 | ||
| 1296 | // Otherwise it's a stream write | |
| 1297 | 0 | var self = this; |
| 1298 | 0 | if (!this.writable) { |
| 1299 | 0 | throw new Error('GridWriteStream is not writable'); |
| 1300 | } | |
| 1301 | ||
| 1302 | // queue data until we open. | |
| 1303 | 0 | if (!this._opened) { |
| 1304 | // Set up a queue to save data until gridstore object is ready | |
| 1305 | 0 | this._q = []; |
| 1306 | 0 | _openStream(self); |
| 1307 | 0 | this._q.push(data); |
| 1308 | 0 | return false; |
| 1309 | } | |
| 1310 | ||
| 1311 | // Push data to queue | |
| 1312 | 0 | this._q.push(data); |
| 1313 | 0 | _flush(this); |
| 1314 | // Return write successful | |
| 1315 | 0 | return true; |
| 1316 | } | |
| 1317 | ||
| 1318 | /** | |
| 1319 | * Handles the destroy part of a stream | |
| 1320 | * @ignore | |
| 1321 | */ | |
| 1322 | 1 | GridStore.prototype.destroy = function destroy() { |
| 1323 | // close and do not emit any more events. queued data is not sent. | |
| 1324 | 0 | if(!this.writable) return; |
| 1325 | 0 | this.readable = false; |
| 1326 | 0 | if(this.writable) { |
| 1327 | 0 | this.writable = false; |
| 1328 | 0 | this._q.length = 0; |
| 1329 | 0 | this.emit('close'); |
| 1330 | } | |
| 1331 | } | |
| 1332 | ||
| 1333 | /** | |
| 1334 | * Handles the destroySoon part of a stream | |
| 1335 | * @ignore | |
| 1336 | */ | |
| 1337 | 1 | GridStore.prototype.destroySoon = function destroySoon() { |
| 1338 | // as soon as write queue is drained, destroy. | |
| 1339 | // may call destroy immediately if no data is queued. | |
| 1340 | 0 | if(!this._q.length) { |
| 1341 | 0 | return this.destroy(); |
| 1342 | } | |
| 1343 | 0 | this._destroying = true; |
| 1344 | } | |
| 1345 | ||
| 1346 | /** | |
| 1347 | * Handles the pipe part of the stream | |
| 1348 | * @ignore | |
| 1349 | */ | |
| 1350 | 1 | GridStore.prototype.pipe = function(destination, options) { |
| 1351 | 0 | var self = this; |
| 1352 | // Open the gridstore | |
| 1353 | 0 | this.open(function(err, result) { |
| 1354 | 0 | if(err) _errorRead(self, err); |
| 1355 | 0 | if(!self.readable) return; |
| 1356 | // Set up the pipe | |
| 1357 | 0 | self._pipe(destination, options); |
| 1358 | // Emit the stream is open | |
| 1359 | 0 | self.emit('open'); |
| 1360 | // Read from the stream | |
| 1361 | 0 | _read(self); |
| 1362 | }) | |
| 1363 | } | |
| 1364 | ||
| 1365 | /** | |
| 1366 | * Internal module methods | |
| 1367 | * @ignore | |
| 1368 | */ | |
| 1369 | 1 | var _read = function _read(self) { |
| 1370 | 0 | if (!self.readable || self.paused || self.reading) { |
| 1371 | 0 | return; |
| 1372 | } | |
| 1373 | ||
| 1374 | 0 | self.reading = true; |
| 1375 | 0 | var stream = self._stream = self.stream(); |
| 1376 | 0 | stream.paused = self.paused; |
| 1377 | ||
| 1378 | 0 | stream.on('data', function (data) { |
| 1379 | 0 | if (self._decoder) { |
| 1380 | 0 | var str = self._decoder.write(data); |
| 1381 | 0 | if (str.length) self.emit('data', str); |
| 1382 | } else { | |
| 1383 | 0 | self.emit('data', data); |
| 1384 | } | |
| 1385 | }); | |
| 1386 | ||
| 1387 | 0 | stream.on('end', function (data) { |
| 1388 | 0 | self.emit('end', data); |
| 1389 | }); | |
| 1390 | ||
| 1391 | 0 | stream.on('error', function (data) { |
| 1392 | 0 | _errorRead(self, data); |
| 1393 | }); | |
| 1394 | ||
| 1395 | 0 | stream.on('close', function (data) { |
| 1396 | 0 | self.emit('close', data); |
| 1397 | }); | |
| 1398 | ||
| 1399 | 0 | self.pause = function () { |
| 1400 | // native doesn't always pause. | |
| 1401 | // bypass its pause() method to hack it | |
| 1402 | 0 | self.paused = stream.paused = true; |
| 1403 | } | |
| 1404 | ||
| 1405 | 0 | self.resume = function () { |
| 1406 | 0 | if(!self.paused) return; |
| 1407 | ||
| 1408 | 0 | self.paused = false; |
| 1409 | 0 | stream.resume(); |
| 1410 | 0 | self.readable = stream.readable; |
| 1411 | } | |
| 1412 | ||
| 1413 | 0 | self.destroy = function () { |
| 1414 | 0 | self.readable = false; |
| 1415 | 0 | stream.destroy(); |
| 1416 | } | |
| 1417 | } | |
| 1418 | ||
| 1419 | /** | |
| 1420 | * pause | |
| 1421 | * @ignore | |
| 1422 | */ | |
| 1423 | 1 | GridStore.prototype.pause = function pause () { |
| 1424 | // Overridden when the GridStore opens. | |
| 1425 | 0 | this.paused = true; |
| 1426 | } | |
| 1427 | ||
| 1428 | /** | |
| 1429 | * resume | |
| 1430 | * @ignore | |
| 1431 | */ | |
| 1432 | 1 | GridStore.prototype.resume = function resume () { |
| 1433 | // Overridden when the GridStore opens. | |
| 1434 | 0 | this.paused = false; |
| 1435 | } | |
| 1436 | ||
| 1437 | /** | |
| 1438 | * Internal module methods | |
| 1439 | * @ignore | |
| 1440 | */ | |
| 1441 | 1 | var _flush = function _flush(self, _force) { |
| 1442 | 0 | if (!self._opened) return; |
| 1443 | 0 | if (!_force && self._flushing) return; |
| 1444 | 0 | self._flushing = true; |
| 1445 | ||
| 1446 | // write the entire q to gridfs | |
| 1447 | 0 | if (!self._q.length) { |
| 1448 | 0 | self._flushing = false; |
| 1449 | 0 | self.emit('drain'); |
| 1450 | ||
| 1451 | 0 | if(self._destroying) { |
| 1452 | 0 | self.destroy(); |
| 1453 | } | |
| 1454 | 0 | return; |
| 1455 | } | |
| 1456 | ||
| 1457 | 0 | self.write(self._q.shift(), function (err, store) { |
| 1458 | 0 | if (err) return _error(self, err); |
| 1459 | 0 | self.emit('progress', store.position); |
| 1460 | 0 | _flush(self, true); |
| 1461 | }); | |
| 1462 | } | |
| 1463 | ||
| 1464 | 1 | var _openStream = function _openStream (self) { |
| 1465 | 0 | if(self._opening == true) return; |
| 1466 | 0 | self._opening = true; |
| 1467 | ||
| 1468 | // Open the store | |
| 1469 | 0 | self.open(function (err, gridstore) { |
| 1470 | 0 | if (err) return _error(self, err); |
| 1471 | 0 | self._opened = true; |
| 1472 | 0 | self.emit('open'); |
| 1473 | 0 | _flush(self); |
| 1474 | }); | |
| 1475 | } | |
| 1476 | ||
| 1477 | 1 | var _error = function _error(self, err) { |
| 1478 | 0 | self.destroy(); |
| 1479 | 0 | self.emit('error', err); |
| 1480 | } | |
| 1481 | ||
| 1482 | 1 | var _errorRead = function _errorRead (self, err) { |
| 1483 | 0 | self.readable = false; |
| 1484 | 0 | self.emit('error', err); |
| 1485 | } | |
| 1486 | ||
| 1487 | /** | |
| 1488 | * @ignore | |
| 1489 | */ | |
| 1490 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 1491 | 0 | return errorOptions == true |
| 1492 | || errorOptions.w > 0 | |
| 1493 | || errorOptions.w == 'majority' | |
| 1494 | || errorOptions.j == true | |
| 1495 | || errorOptions.journal == true | |
| 1496 | || errorOptions.fsync == true | |
| 1497 | } | |
| 1498 | ||
| 1499 | /** | |
| 1500 | * @ignore | |
| 1501 | */ | |
| 1502 | 1 | var _setWriteConcernHash = function(options) { |
| 1503 | 0 | var finalOptions = {}; |
| 1504 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 1505 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 1506 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 1507 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 1508 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 1509 | 0 | return finalOptions; |
| 1510 | } | |
| 1511 | ||
| 1512 | /** | |
| 1513 | * @ignore | |
| 1514 | */ | |
| 1515 | 1 | var _getWriteConcern = function(self, options, callback) { |
| 1516 | // Final options | |
| 1517 | 0 | var finalOptions = {w:1}; |
| 1518 | 0 | options = options || {}; |
| 1519 | // Local options verification | |
| 1520 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 1521 | 0 | finalOptions = _setWriteConcernHash(options); |
| 1522 | 0 | } else if(typeof options.safe == "boolean") { |
| 1523 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 1524 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 1525 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 1526 | 0 | } else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') { |
| 1527 | 0 | finalOptions = _setWriteConcernHash(self.db.safe); |
| 1528 | 0 | } else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') { |
| 1529 | 0 | finalOptions = _setWriteConcernHash(self.db.options); |
| 1530 | 0 | } else if(typeof self.db.safe == "boolean") { |
| 1531 | 0 | finalOptions = {w: (self.db.safe ? 1 : 0)}; |
| 1532 | } | |
| 1533 | ||
| 1534 | // Ensure we don't have an invalid combination of write concerns | |
| 1535 | 0 | if(finalOptions.w < 1 |
| 1536 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 1537 | ||
| 1538 | // Return the options | |
| 1539 | 0 | return finalOptions; |
| 1540 | } | |
| 1541 | ||
| 1542 | /** | |
| 1543 | * @ignore | |
| 1544 | * @api private | |
| 1545 | */ | |
| 1546 | 1 | exports.GridStore = GridStore; |
| 1547 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Stream = require('stream').Stream, |
| 2 | timers = require('timers'), | |
| 3 | util = require('util'); | |
| 4 | ||
| 5 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 6 | 1 | var processor = require('../utils').processor(); |
| 7 | ||
| 8 | /** | |
| 9 | * ReadStream | |
| 10 | * | |
| 11 | * Returns a stream interface for the **file**. | |
| 12 | * | |
| 13 | * Events | |
| 14 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 15 | * - **end** {function() {}} the end event triggers when there is no more documents available. | |
| 16 | * - **close** {function() {}} the close event triggers when the stream is closed. | |
| 17 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 18 | * | |
| 19 | * @class Represents a GridFS File Stream. | |
| 20 | * @param {Boolean} autoclose automatically close file when the stream reaches the end. | |
| 21 | * @param {GridStore} cursor a cursor object that the stream wraps. | |
| 22 | * @return {ReadStream} | |
| 23 | */ | |
| 24 | 1 | function ReadStream(autoclose, gstore) { |
| 25 | 0 | if (!(this instanceof ReadStream)) return new ReadStream(autoclose, gstore); |
| 26 | 0 | Stream.call(this); |
| 27 | ||
| 28 | 0 | this.autoclose = !!autoclose; |
| 29 | 0 | this.gstore = gstore; |
| 30 | ||
| 31 | 0 | this.finalLength = gstore.length - gstore.position; |
| 32 | 0 | this.completedLength = 0; |
| 33 | 0 | this.currentChunkNumber = gstore.currentChunk.chunkNumber; |
| 34 | ||
| 35 | 0 | this.paused = false; |
| 36 | 0 | this.readable = true; |
| 37 | 0 | this.pendingChunk = null; |
| 38 | 0 | this.executing = false; |
| 39 | 0 | this.destroyed = false; |
| 40 | ||
| 41 | // Calculate the number of chunks | |
| 42 | 0 | this.numberOfChunks = Math.ceil(gstore.length/gstore.chunkSize); |
| 43 | ||
| 44 | // This seek start position inside the current chunk | |
| 45 | 0 | this.seekStartPosition = gstore.position - (this.currentChunkNumber * gstore.chunkSize); |
| 46 | ||
| 47 | 0 | var self = this; |
| 48 | 0 | processor(function() { |
| 49 | 0 | self._execute(); |
| 50 | }); | |
| 51 | }; | |
| 52 | ||
| 53 | /** | |
| 54 | * Inherit from Stream | |
| 55 | * @ignore | |
| 56 | * @api private | |
| 57 | */ | |
| 58 | 1 | ReadStream.prototype.__proto__ = Stream.prototype; |
| 59 | ||
| 60 | /** | |
| 61 | * Flag stating whether or not this stream is readable. | |
| 62 | */ | |
| 63 | 1 | ReadStream.prototype.readable; |
| 64 | ||
| 65 | /** | |
| 66 | * Flag stating whether or not this stream is paused. | |
| 67 | */ | |
| 68 | 1 | ReadStream.prototype.paused; |
| 69 | ||
| 70 | /** | |
| 71 | * @ignore | |
| 72 | * @api private | |
| 73 | */ | |
| 74 | 1 | ReadStream.prototype._execute = function() { |
| 75 | 0 | if(this.paused === true || this.readable === false) { |
| 76 | 0 | return; |
| 77 | } | |
| 78 | ||
| 79 | 0 | var gstore = this.gstore; |
| 80 | 0 | var self = this; |
| 81 | // Set that we are executing | |
| 82 | 0 | this.executing = true; |
| 83 | ||
| 84 | 0 | var last = false; |
| 85 | 0 | var toRead = 0; |
| 86 | ||
| 87 | 0 | if(gstore.currentChunk.chunkNumber >= (this.numberOfChunks - 1)) { |
| 88 | 0 | self.executing = false; |
| 89 | 0 | last = true; |
| 90 | } | |
| 91 | ||
| 92 | // Data setup | |
| 93 | 0 | var data = null; |
| 94 | ||
| 95 | // Read a slice (with seek set if none) | |
| 96 | 0 | if(this.seekStartPosition > 0 && (gstore.currentChunk.length() - this.seekStartPosition) > 0) { |
| 97 | 0 | data = gstore.currentChunk.readSlice(gstore.currentChunk.length() - this.seekStartPosition); |
| 98 | 0 | this.seekStartPosition = 0; |
| 99 | } else { | |
| 100 | 0 | data = gstore.currentChunk.readSlice(gstore.currentChunk.length()); |
| 101 | } | |
| 102 | ||
| 103 | // Return the data | |
| 104 | 0 | if(data != null && gstore.currentChunk.chunkNumber == self.currentChunkNumber) { |
| 105 | 0 | self.currentChunkNumber = self.currentChunkNumber + 1; |
| 106 | 0 | self.completedLength += data.length; |
| 107 | 0 | self.pendingChunk = null; |
| 108 | 0 | self.emit("data", data); |
| 109 | } | |
| 110 | ||
| 111 | 0 | if(last === true) { |
| 112 | 0 | self.readable = false; |
| 113 | 0 | self.emit("end"); |
| 114 | ||
| 115 | 0 | if(self.autoclose === true) { |
| 116 | 0 | if(gstore.mode[0] == "w") { |
| 117 | 0 | gstore.close(function(err, doc) { |
| 118 | 0 | if (err) { |
| 119 | 0 | console.log("############################## 0") |
| 120 | 0 | self.emit("error", err); |
| 121 | 0 | return; |
| 122 | } | |
| 123 | 0 | self.readable = false; |
| 124 | 0 | self.destroyed = true; |
| 125 | 0 | self.emit("close", doc); |
| 126 | }); | |
| 127 | } else { | |
| 128 | 0 | self.readable = false; |
| 129 | 0 | self.destroyed = true; |
| 130 | 0 | self.emit("close"); |
| 131 | } | |
| 132 | } | |
| 133 | } else { | |
| 134 | 0 | gstore._nthChunk(gstore.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 135 | 0 | if(err) { |
| 136 | 0 | self.readable = false; |
| 137 | 0 | if(self.listeners("error").length > 0) |
| 138 | 0 | self.emit("error", err); |
| 139 | 0 | self.executing = false; |
| 140 | 0 | return; |
| 141 | } | |
| 142 | ||
| 143 | 0 | self.pendingChunk = chunk; |
| 144 | 0 | if(self.paused === true) { |
| 145 | 0 | self.executing = false; |
| 146 | 0 | return; |
| 147 | } | |
| 148 | ||
| 149 | 0 | gstore.currentChunk = self.pendingChunk; |
| 150 | 0 | self._execute(); |
| 151 | }); | |
| 152 | } | |
| 153 | }; | |
| 154 | ||
| 155 | /** | |
| 156 | * Pauses this stream, then no farther events will be fired. | |
| 157 | * | |
| 158 | * @ignore | |
| 159 | * @api public | |
| 160 | */ | |
| 161 | 1 | ReadStream.prototype.pause = function() { |
| 162 | 0 | if(!this.executing) { |
| 163 | 0 | this.paused = true; |
| 164 | } | |
| 165 | }; | |
| 166 | ||
| 167 | /** | |
| 168 | * Destroys the stream, then no farther events will be fired. | |
| 169 | * | |
| 170 | * @ignore | |
| 171 | * @api public | |
| 172 | */ | |
| 173 | 1 | ReadStream.prototype.destroy = function() { |
| 174 | 0 | if(this.destroyed) return; |
| 175 | 0 | this.destroyed = true; |
| 176 | 0 | this.readable = false; |
| 177 | // Emit close event | |
| 178 | 0 | this.emit("close"); |
| 179 | }; | |
| 180 | ||
| 181 | /** | |
| 182 | * Resumes this stream. | |
| 183 | * | |
| 184 | * @ignore | |
| 185 | * @api public | |
| 186 | */ | |
| 187 | 1 | ReadStream.prototype.resume = function() { |
| 188 | 0 | if(this.paused === false || !this.readable) { |
| 189 | 0 | return; |
| 190 | } | |
| 191 | ||
| 192 | 0 | this.paused = false; |
| 193 | 0 | var self = this; |
| 194 | 0 | processor(function() { |
| 195 | 0 | self._execute(); |
| 196 | }); | |
| 197 | }; | |
| 198 | ||
| 199 | 1 | exports.ReadStream = ReadStream; |
| 200 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | try { |
| 2 | 1 | exports.BSONPure = require('bson').BSONPure; |
| 3 | 1 | exports.BSONNative = require('bson').BSONNative; |
| 4 | } catch(err) { | |
| 5 | // do nothing | |
| 6 | } | |
| 7 | ||
| 8 | // export the driver version | |
| 9 | 1 | exports.version = require('../../package').version; |
| 10 | ||
| 11 | 1 | [ 'commands/base_command' |
| 12 | , 'admin' | |
| 13 | , 'collection' | |
| 14 | , 'connection/read_preference' | |
| 15 | , 'connection/connection' | |
| 16 | , 'connection/server' | |
| 17 | , 'connection/mongos' | |
| 18 | , 'connection/repl_set/repl_set' | |
| 19 | , 'mongo_client' | |
| 20 | , 'cursor' | |
| 21 | , 'db' | |
| 22 | , 'mongo_client' | |
| 23 | , 'gridfs/grid' | |
| 24 | , 'gridfs/chunk' | |
| 25 | , 'gridfs/gridstore'].forEach(function (path) { | |
| 26 | 15 | var module = require('./' + path); |
| 27 | 15 | for (var i in module) { |
| 28 | 16 | exports[i] = module[i]; |
| 29 | } | |
| 30 | }); | |
| 31 | ||
| 32 | // backwards compat | |
| 33 | 1 | exports.ReplSetServers = exports.ReplSet; |
| 34 | // Add BSON Classes | |
| 35 | 1 | exports.Binary = require('bson').Binary; |
| 36 | 1 | exports.Code = require('bson').Code; |
| 37 | 1 | exports.DBRef = require('bson').DBRef; |
| 38 | 1 | exports.Double = require('bson').Double; |
| 39 | 1 | exports.Long = require('bson').Long; |
| 40 | 1 | exports.MinKey = require('bson').MinKey; |
| 41 | 1 | exports.MaxKey = require('bson').MaxKey; |
| 42 | 1 | exports.ObjectID = require('bson').ObjectID; |
| 43 | 1 | exports.Symbol = require('bson').Symbol; |
| 44 | 1 | exports.Timestamp = require('bson').Timestamp; |
| 45 | // Add BSON Parser | |
| 46 | 1 | exports.BSON = require('bson').BSONPure.BSON; |
| 47 | ||
| 48 | // Get the Db object | |
| 49 | 1 | var Db = require('./db').Db; |
| 50 | // Set up the connect function | |
| 51 | 1 | var connect = Db.connect; |
| 52 | 1 | var obj = connect; |
| 53 | // Map all values to the exports value | |
| 54 | 1 | for(var name in exports) { |
| 55 | 30 | obj[name] = exports[name]; |
| 56 | } | |
| 57 | ||
| 58 | // Add the pure and native backward compatible functions | |
| 59 | 1 | exports.pure = exports.native = function() { |
| 60 | 0 | return obj; |
| 61 | } | |
| 62 | ||
| 63 | // Map all values to the exports value | |
| 64 | 1 | for(var name in exports) { |
| 65 | 32 | connect[name] = exports[name]; |
| 66 | } | |
| 67 | ||
| 68 | // Set our exports to be the connect function | |
| 69 | 1 | module.exports = connect; |
| 70 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Db = require('./db').Db |
| 2 | , Server = require('./connection/server').Server | |
| 3 | , Mongos = require('./connection/mongos').Mongos | |
| 4 | , ReplSet = require('./connection/repl_set/repl_set').ReplSet | |
| 5 | , ReadPreference = require('./connection/read_preference').ReadPreference | |
| 6 | , inherits = require('util').inherits | |
| 7 | , EventEmitter = require('events').EventEmitter | |
| 8 | , parse = require('./connection/url_parser').parse; | |
| 9 | ||
| 10 | /** | |
| 11 | * Create a new MongoClient instance. | |
| 12 | * | |
| 13 | * Options | |
| 14 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 15 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 16 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 17 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 18 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 19 | * - **native_parser** {Boolean, default:false}, use c++ bson parser. | |
| 20 | * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. | |
| 21 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 22 | * - **serializeFunctions** {Boolean, default:false}, serialize functions. | |
| 23 | * - **raw** {Boolean, default:false}, peform operations using raw bson buffers. | |
| 24 | * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. | |
| 25 | * - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries. | |
| 26 | * - **numberOfRetries** {Number, default:5}, number of retries off connection. | |
| 27 | * - **bufferMaxEntries** {Boolean, default: -1}, sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited | |
| 28 | * | |
| 29 | * Deprecated Options | |
| 30 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 31 | * | |
| 32 | * @class Represents a MongoClient | |
| 33 | * @param {Object} serverConfig server config object. | |
| 34 | * @param {Object} [options] additional options for the collection. | |
| 35 | */ | |
| 36 | 1 | function MongoClient(serverConfig, options) { |
| 37 | 0 | if(serverConfig != null) { |
| 38 | 0 | options = options == null ? {} : options; |
| 39 | // If no write concern is set set the default to w:1 | |
| 40 | 0 | if(options != null && !options.journal && !options.w && !options.fsync) { |
| 41 | 0 | options.w = 1; |
| 42 | } | |
| 43 | ||
| 44 | // The internal db instance we are wrapping | |
| 45 | 0 | this._db = new Db('test', serverConfig, options); |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * @ignore | |
| 51 | */ | |
| 52 | 1 | inherits(MongoClient, EventEmitter); |
| 53 | ||
| 54 | /** | |
| 55 | * Connect to MongoDB using a url as documented at | |
| 56 | * | |
| 57 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 58 | * | |
| 59 | * Options | |
| 60 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 61 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 62 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 63 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 64 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 65 | * | |
| 66 | * @param {String} url connection url for MongoDB. | |
| 67 | * @param {Object} [options] optional options for insert command | |
| 68 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured. | |
| 69 | * @return {null} | |
| 70 | * @api public | |
| 71 | */ | |
| 72 | 1 | MongoClient.prototype.connect = function(url, options, callback) { |
| 73 | 0 | var self = this; |
| 74 | ||
| 75 | 0 | if(typeof options == 'function') { |
| 76 | 0 | callback = options; |
| 77 | 0 | options = {}; |
| 78 | } | |
| 79 | ||
| 80 | 0 | MongoClient.connect(url, options, function(err, db) { |
| 81 | 0 | if(err) return callback(err, db); |
| 82 | // Store internal db instance reference | |
| 83 | 0 | self._db = db; |
| 84 | // Emit open and perform callback | |
| 85 | 0 | self.emit("open", err, db); |
| 86 | 0 | callback(err, db); |
| 87 | }); | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Initialize the database connection. | |
| 92 | * | |
| 93 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the connected mongoclient or null if an error occured. | |
| 94 | * @return {null} | |
| 95 | * @api public | |
| 96 | */ | |
| 97 | 1 | MongoClient.prototype.open = function(callback) { |
| 98 | // Self reference | |
| 99 | 0 | var self = this; |
| 100 | // Open the db | |
| 101 | 0 | this._db.open(function(err, db) { |
| 102 | 0 | if(err) return callback(err, null); |
| 103 | // Emit open event | |
| 104 | 0 | self.emit("open", err, db); |
| 105 | // Callback | |
| 106 | 0 | callback(null, self); |
| 107 | }) | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. | |
| 112 | * | |
| 113 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the close method or null if an error occured. | |
| 114 | * @return {null} | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | MongoClient.prototype.close = function(callback) { |
| 118 | 0 | this._db.close(callback); |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Create a new Db instance sharing the current socket connections. | |
| 123 | * | |
| 124 | * @param {String} dbName the name of the database we want to use. | |
| 125 | * @return {Db} a db instance using the new database. | |
| 126 | * @api public | |
| 127 | */ | |
| 128 | 1 | MongoClient.prototype.db = function(dbName) { |
| 129 | 0 | return this._db.db(dbName); |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Connect to MongoDB using a url as documented at | |
| 134 | * | |
| 135 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 136 | * | |
| 137 | * Options | |
| 138 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 139 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 140 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 141 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 142 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 143 | * | |
| 144 | * @param {String} url connection url for MongoDB. | |
| 145 | * @param {Object} [options] optional options for insert command | |
| 146 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured. | |
| 147 | * @return {null} | |
| 148 | * @api public | |
| 149 | */ | |
| 150 | 1 | MongoClient.connect = function(url, options, callback) { |
| 151 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 152 | 0 | callback = typeof args[args.length - 1] == 'function' ? args.pop() : null; |
| 153 | 0 | options = args.length ? args.shift() : null; |
| 154 | 0 | options = options || {}; |
| 155 | ||
| 156 | // Set default empty server options | |
| 157 | 0 | var serverOptions = options.server || {}; |
| 158 | 0 | var mongosOptions = options.mongos || {}; |
| 159 | 0 | var replSetServersOptions = options.replSet || options.replSetServers || {}; |
| 160 | 0 | var dbOptions = options.db || {}; |
| 161 | ||
| 162 | // If callback is null throw an exception | |
| 163 | 0 | if(callback == null) |
| 164 | 0 | throw new Error("no callback function provided"); |
| 165 | ||
| 166 | // Parse the string | |
| 167 | 0 | var object = parse(url, options); |
| 168 | // Merge in any options for db in options object | |
| 169 | 0 | if(dbOptions) { |
| 170 | 0 | for(var name in dbOptions) object.db_options[name] = dbOptions[name]; |
| 171 | } | |
| 172 | ||
| 173 | // Added the url to the options | |
| 174 | 0 | object.db_options.url = url; |
| 175 | ||
| 176 | // Merge in any options for server in options object | |
| 177 | 0 | if(serverOptions) { |
| 178 | 0 | for(var name in serverOptions) object.server_options[name] = serverOptions[name]; |
| 179 | } | |
| 180 | ||
| 181 | // Merge in any replicaset server options | |
| 182 | 0 | if(replSetServersOptions) { |
| 183 | 0 | for(var name in replSetServersOptions) object.rs_options[name] = replSetServersOptions[name]; |
| 184 | } | |
| 185 | ||
| 186 | // Merge in any replicaset server options | |
| 187 | 0 | if(mongosOptions) { |
| 188 | 0 | for(var name in mongosOptions) object.mongos_options[name] = mongosOptions[name]; |
| 189 | } | |
| 190 | ||
| 191 | // We need to ensure that the list of servers are only either direct members or mongos | |
| 192 | // they cannot be a mix of monogs and mongod's | |
| 193 | 0 | var totalNumberOfServers = object.servers.length; |
| 194 | 0 | var totalNumberOfMongosServers = 0; |
| 195 | 0 | var totalNumberOfMongodServers = 0; |
| 196 | 0 | var serverConfig = null; |
| 197 | 0 | var errorServers = {}; |
| 198 | ||
| 199 | // Failure modes | |
| 200 | 0 | if(object.servers.length == 0) throw new Error("connection string must contain at least one seed host"); |
| 201 | ||
| 202 | // If we have no db setting for the native parser try to set the c++ one first | |
| 203 | 0 | object.db_options.native_parser = _setNativeParser(object.db_options); |
| 204 | // If no auto_reconnect is set, set it to true as default for single servers | |
| 205 | 0 | if(typeof object.server_options.auto_reconnect != 'boolean') { |
| 206 | 0 | object.server_options.auto_reconnect = true; |
| 207 | } | |
| 208 | ||
| 209 | // If we have more than a server, it could be replicaset or mongos list | |
| 210 | // need to verify that it's one or the other and fail if it's a mix | |
| 211 | // Connect to all servers and run ismaster | |
| 212 | 0 | for(var i = 0; i < object.servers.length; i++) { |
| 213 | // Set up socket options | |
| 214 | 0 | var _server_options = { |
| 215 | poolSize:1 | |
| 216 | , socketOptions: { | |
| 217 | connectTimeoutMS:30000 | |
| 218 | , socketTimeoutMS: 30000 | |
| 219 | } | |
| 220 | , auto_reconnect:false}; | |
| 221 | ||
| 222 | // Ensure we have ssl setup for the servers | |
| 223 | 0 | if(object.rs_options.ssl) { |
| 224 | 0 | _server_options.ssl = object.rs_options.ssl; |
| 225 | 0 | _server_options.sslValidate = object.rs_options.sslValidate; |
| 226 | 0 | _server_options.sslCA = object.rs_options.sslCA; |
| 227 | 0 | _server_options.sslCert = object.rs_options.sslCert; |
| 228 | 0 | _server_options.sslKey = object.rs_options.sslKey; |
| 229 | 0 | _server_options.sslPass = object.rs_options.sslPass; |
| 230 | 0 | } else if(object.server_options.ssl) { |
| 231 | 0 | _server_options.ssl = object.server_options.ssl; |
| 232 | 0 | _server_options.sslValidate = object.server_options.sslValidate; |
| 233 | 0 | _server_options.sslCA = object.server_options.sslCA; |
| 234 | 0 | _server_options.sslCert = object.server_options.sslCert; |
| 235 | 0 | _server_options.sslKey = object.server_options.sslKey; |
| 236 | 0 | _server_options.sslPass = object.server_options.sslPass; |
| 237 | } | |
| 238 | ||
| 239 | // Set up the Server object | |
| 240 | 0 | var _server = object.servers[i].domain_socket |
| 241 | ? new Server(object.servers[i].domain_socket, _server_options) | |
| 242 | : new Server(object.servers[i].host, object.servers[i].port, _server_options); | |
| 243 | ||
| 244 | 0 | var connectFunction = function(__server) { |
| 245 | // Attempt connect | |
| 246 | 0 | new Db(object.dbName, __server, {safe:false, native_parser:false}).open(function(err, db) { |
| 247 | // Update number of servers | |
| 248 | 0 | totalNumberOfServers = totalNumberOfServers - 1; |
| 249 | // If no error do the correct checks | |
| 250 | 0 | if(!err) { |
| 251 | // Close the connection | |
| 252 | 0 | db.close(true); |
| 253 | 0 | var isMasterDoc = db.serverConfig.isMasterDoc; |
| 254 | // Check what type of server we have | |
| 255 | 0 | if(isMasterDoc.setName) totalNumberOfMongodServers++; |
| 256 | 0 | if(isMasterDoc.msg && isMasterDoc.msg == "isdbgrid") totalNumberOfMongosServers++; |
| 257 | } else { | |
| 258 | 0 | errorServers[__server.host + ":" + __server.port] = __server; |
| 259 | } | |
| 260 | ||
| 261 | 0 | if(totalNumberOfServers == 0) { |
| 262 | // If we have a mix of mongod and mongos, throw an error | |
| 263 | 0 | if(totalNumberOfMongosServers > 0 && totalNumberOfMongodServers > 0) { |
| 264 | 0 | return process.nextTick(function() { |
| 265 | 0 | try { |
| 266 | 0 | callback(new Error("cannot combine a list of replicaset seeds and mongos seeds")); |
| 267 | } catch (err) { | |
| 268 | 0 | if(db) db.close(); |
| 269 | 0 | throw err |
| 270 | } | |
| 271 | }) | |
| 272 | } | |
| 273 | ||
| 274 | 0 | if(totalNumberOfMongodServers == 0 && object.servers.length == 1) { |
| 275 | 0 | var obj = object.servers[0]; |
| 276 | 0 | serverConfig = obj.domain_socket ? |
| 277 | new Server(obj.domain_socket, object.server_options) | |
| 278 | : new Server(obj.host, obj.port, object.server_options); | |
| 279 | 0 | } else if(totalNumberOfMongodServers > 0 || totalNumberOfMongosServers > 0) { |
| 280 | 0 | var finalServers = object.servers |
| 281 | .filter(function(serverObj) { | |
| 282 | 0 | return errorServers[serverObj.host + ":" + serverObj.port] == null; |
| 283 | }) | |
| 284 | .map(function(serverObj) { | |
| 285 | 0 | return new Server(serverObj.host, serverObj.port, object.server_options); |
| 286 | }); | |
| 287 | // Clean out any error servers | |
| 288 | 0 | errorServers = {}; |
| 289 | // Set up the final configuration | |
| 290 | 0 | if(totalNumberOfMongodServers > 0) { |
| 291 | 0 | serverConfig = new ReplSet(finalServers, object.rs_options); |
| 292 | } else { | |
| 293 | 0 | serverConfig = new Mongos(finalServers, object.mongos_options); |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 297 | 0 | if(serverConfig == null) { |
| 298 | 0 | return process.nextTick(function() { |
| 299 | 0 | try { |
| 300 | 0 | callback(new Error("Could not locate any valid servers in initial seed list")); |
| 301 | } catch (err) { | |
| 302 | 0 | if(db) db.close(); |
| 303 | 0 | throw err |
| 304 | } | |
| 305 | }); | |
| 306 | } | |
| 307 | // Ensure no firing off open event before we are ready | |
| 308 | 0 | serverConfig.emitOpen = false; |
| 309 | // Set up all options etc and connect to the database | |
| 310 | 0 | _finishConnecting(serverConfig, object, options, callback) |
| 311 | } | |
| 312 | }); | |
| 313 | } | |
| 314 | ||
| 315 | // Wrap the context of the call | |
| 316 | 0 | connectFunction(_server); |
| 317 | } | |
| 318 | } | |
| 319 | ||
| 320 | 1 | var _setNativeParser = function(db_options) { |
| 321 | 0 | if(typeof db_options.native_parser == 'boolean') return db_options.native_parser; |
| 322 | ||
| 323 | 0 | try { |
| 324 | 0 | require('bson').BSONNative.BSON; |
| 325 | 0 | return true; |
| 326 | } catch(err) { | |
| 327 | 0 | return false; |
| 328 | } | |
| 329 | } | |
| 330 | ||
| 331 | 1 | var _finishConnecting = function(serverConfig, object, options, callback) { |
| 332 | // Safe settings | |
| 333 | 0 | var safe = {}; |
| 334 | // Build the safe parameter if needed | |
| 335 | 0 | if(object.db_options.journal) safe.j = object.db_options.journal; |
| 336 | 0 | if(object.db_options.w) safe.w = object.db_options.w; |
| 337 | 0 | if(object.db_options.fsync) safe.fsync = object.db_options.fsync; |
| 338 | 0 | if(object.db_options.wtimeoutMS) safe.wtimeout = object.db_options.wtimeoutMS; |
| 339 | ||
| 340 | // If we have a read Preference set | |
| 341 | 0 | if(object.db_options.read_preference) { |
| 342 | 0 | var readPreference = new ReadPreference(object.db_options.read_preference); |
| 343 | // If we have the tags set up | |
| 344 | 0 | if(object.db_options.read_preference_tags) |
| 345 | 0 | readPreference = new ReadPreference(object.db_options.read_preference, object.db_options.read_preference_tags); |
| 346 | // Add the read preference | |
| 347 | 0 | object.db_options.readPreference = readPreference; |
| 348 | } | |
| 349 | ||
| 350 | // No safe mode if no keys | |
| 351 | 0 | if(Object.keys(safe).length == 0) safe = false; |
| 352 | ||
| 353 | // Add the safe object | |
| 354 | 0 | object.db_options.safe = safe; |
| 355 | ||
| 356 | // Get the socketTimeoutMS | |
| 357 | 0 | var socketTimeoutMS = object.server_options.socketOptions.socketTimeoutMS || 0; |
| 358 | ||
| 359 | // If we have a replset, override with replicaset socket timeout option if available | |
| 360 | 0 | if(serverConfig instanceof ReplSet) { |
| 361 | 0 | socketTimeoutMS = object.rs_options.socketOptions.socketTimeoutMS || socketTimeoutMS; |
| 362 | } | |
| 363 | ||
| 364 | // Set socketTimeout to the same as the connectTimeoutMS or 30 sec | |
| 365 | 0 | serverConfig.connectTimeoutMS = serverConfig.connectTimeoutMS || 30000; |
| 366 | 0 | serverConfig.socketTimeoutMS = serverConfig.connectTimeoutMS; |
| 367 | ||
| 368 | // Set up the db options | |
| 369 | 0 | var db = new Db(object.dbName, serverConfig, object.db_options); |
| 370 | // Open the db | |
| 371 | 0 | db.open(function(err, db){ |
| 372 | 0 | if(err) { |
| 373 | 0 | return process.nextTick(function() { |
| 374 | 0 | try { |
| 375 | 0 | callback(err, null); |
| 376 | } catch (err) { | |
| 377 | 0 | if(db) db.close(); |
| 378 | 0 | throw err |
| 379 | } | |
| 380 | }); | |
| 381 | } | |
| 382 | ||
| 383 | // Reset the socket timeout | |
| 384 | 0 | serverConfig.socketTimeoutMS = socketTimeoutMS || 0; |
| 385 | ||
| 386 | // Set the provided write concern or fall back to w:1 as default | |
| 387 | 0 | if(db.options !== null && !db.options.safe && !db.options.journal |
| 388 | && !db.options.w && !db.options.fsync && typeof db.options.w != 'number' | |
| 389 | && (db.options.safe == false && object.db_options.url.indexOf("safe=") == -1)) { | |
| 390 | 0 | db.options.w = 1; |
| 391 | } | |
| 392 | ||
| 393 | 0 | if(err == null && object.auth){ |
| 394 | // What db to authenticate against | |
| 395 | 0 | var authentication_db = db; |
| 396 | 0 | if(object.db_options && object.db_options.authSource) { |
| 397 | 0 | authentication_db = db.db(object.db_options.authSource); |
| 398 | } | |
| 399 | ||
| 400 | // Build options object | |
| 401 | 0 | var options = {}; |
| 402 | 0 | if(object.db_options.authMechanism) options.authMechanism = object.db_options.authMechanism; |
| 403 | 0 | if(object.db_options.gssapiServiceName) options.gssapiServiceName = object.db_options.gssapiServiceName; |
| 404 | ||
| 405 | // Authenticate | |
| 406 | 0 | authentication_db.authenticate(object.auth.user, object.auth.password, options, function(err, success){ |
| 407 | 0 | if(success){ |
| 408 | 0 | process.nextTick(function() { |
| 409 | 0 | try { |
| 410 | 0 | callback(null, db); |
| 411 | } catch (err) { | |
| 412 | 0 | if(db) db.close(); |
| 413 | 0 | throw err |
| 414 | } | |
| 415 | }); | |
| 416 | } else { | |
| 417 | 0 | if(db) db.close(); |
| 418 | 0 | process.nextTick(function() { |
| 419 | 0 | try { |
| 420 | 0 | callback(err ? err : new Error('Could not authenticate user ' + auth[0]), null); |
| 421 | } catch (err) { | |
| 422 | 0 | if(db) db.close(); |
| 423 | 0 | throw err |
| 424 | } | |
| 425 | }); | |
| 426 | } | |
| 427 | }); | |
| 428 | } else { | |
| 429 | 0 | process.nextTick(function() { |
| 430 | 0 | try { |
| 431 | 0 | callback(err, db); |
| 432 | } catch (err) { | |
| 433 | 0 | if(db) db.close(); |
| 434 | 0 | throw err |
| 435 | } | |
| 436 | }) | |
| 437 | } | |
| 438 | }); | |
| 439 | } | |
| 440 | ||
| 441 | 1 | exports.MongoClient = MongoClient; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('bson').Long |
| 2 | , timers = require('timers'); | |
| 3 | ||
| 4 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 5 | 1 | var processor = require('../utils').processor(); |
| 6 | ||
| 7 | /** | |
| 8 | Reply message from mongo db | |
| 9 | **/ | |
| 10 | 1 | var MongoReply = exports.MongoReply = function() { |
| 11 | 0 | this.documents = []; |
| 12 | 0 | this.index = 0; |
| 13 | }; | |
| 14 | ||
| 15 | 1 | MongoReply.prototype.parseHeader = function(binary_reply, bson) { |
| 16 | // Unpack the standard header first | |
| 17 | 0 | this.messageLength = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 18 | 0 | this.index = this.index + 4; |
| 19 | // Fetch the request id for this reply | |
| 20 | 0 | this.requestId = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 21 | 0 | this.index = this.index + 4; |
| 22 | // Fetch the id of the request that triggered the response | |
| 23 | 0 | this.responseTo = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 24 | // Skip op-code field | |
| 25 | 0 | this.index = this.index + 4 + 4; |
| 26 | // Unpack the reply message | |
| 27 | 0 | this.responseFlag = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 28 | 0 | this.index = this.index + 4; |
| 29 | // Unpack the cursor id (a 64 bit long integer) | |
| 30 | 0 | var low_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 31 | 0 | this.index = this.index + 4; |
| 32 | 0 | var high_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 33 | 0 | this.index = this.index + 4; |
| 34 | 0 | this.cursorId = new Long(low_bits, high_bits); |
| 35 | // Unpack the starting from | |
| 36 | 0 | this.startingFrom = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 37 | 0 | this.index = this.index + 4; |
| 38 | // Unpack the number of objects returned | |
| 39 | 0 | this.numberReturned = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 40 | 0 | this.index = this.index + 4; |
| 41 | } | |
| 42 | ||
| 43 | 1 | MongoReply.prototype.parseBody = function(binary_reply, bson, raw, callback) { |
| 44 | 0 | raw = raw == null ? false : raw; |
| 45 | ||
| 46 | 0 | try { |
| 47 | // Let's unpack all the bson documents, deserialize them and store them | |
| 48 | 0 | for(var object_index = 0; object_index < this.numberReturned; object_index++) { |
| 49 | 0 | var _options = {promoteLongs: bson.promoteLongs}; |
| 50 | ||
| 51 | // Read the size of the bson object | |
| 52 | 0 | var bsonObjectSize = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 53 | ||
| 54 | // If we are storing the raw responses to pipe straight through | |
| 55 | 0 | if(raw) { |
| 56 | // Deserialize the object and add to the documents array | |
| 57 | 0 | this.documents.push(binary_reply.slice(this.index, this.index + bsonObjectSize)); |
| 58 | } else { | |
| 59 | // Deserialize the object and add to the documents array | |
| 60 | 0 | this.documents.push(bson.deserialize(binary_reply.slice(this.index, this.index + bsonObjectSize), _options)); |
| 61 | } | |
| 62 | ||
| 63 | // Adjust binary index to point to next block of binary bson data | |
| 64 | 0 | this.index = this.index + bsonObjectSize; |
| 65 | } | |
| 66 | ||
| 67 | // No error return | |
| 68 | 0 | callback(null); |
| 69 | } catch(err) { | |
| 70 | 0 | return callback(err); |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | 1 | MongoReply.prototype.is_error = function(){ |
| 75 | 0 | if(this.documents.length == 1) { |
| 76 | 0 | return this.documents[0].ok == 1 ? false : true; |
| 77 | } | |
| 78 | 0 | return false; |
| 79 | }; | |
| 80 | ||
| 81 | 1 | MongoReply.prototype.error_message = function() { |
| 82 | 0 | return this.documents.length == 1 && this.documents[0].ok == 1 ? '' : this.documents[0].errmsg; |
| 83 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Cursor2 = require('./cursor').Cursor |
| 2 | , Readable = require('stream').Readable | |
| 3 | , utils = require('./utils') | |
| 4 | , inherits = require('util').inherits; | |
| 5 | ||
| 6 | 1 | var Cursor = function Cursor(_scope_options, _cursor) { |
| 7 | // | |
| 8 | // Backward compatible methods | |
| 9 | 0 | this.toArray = function(callback) { |
| 10 | 0 | return _cursor.toArray(callback); |
| 11 | } | |
| 12 | ||
| 13 | 0 | this.each = function(callback) { |
| 14 | 0 | return _cursor.each(callback); |
| 15 | } | |
| 16 | ||
| 17 | 0 | this.next = function(callback) { |
| 18 | 0 | this.nextObject(callback); |
| 19 | } | |
| 20 | ||
| 21 | 0 | this.nextObject = function(callback) { |
| 22 | 0 | return _cursor.nextObject(callback); |
| 23 | } | |
| 24 | ||
| 25 | 0 | this.setReadPreference = function(readPreference, callback) { |
| 26 | 0 | _scope_options.readPreference = {readPreference: readPreference}; |
| 27 | 0 | _cursor.setReadPreference(readPreference, callback); |
| 28 | 0 | return this; |
| 29 | } | |
| 30 | ||
| 31 | 0 | this.batchSize = function(batchSize, callback) { |
| 32 | 0 | _scope_options.batchSize = batchSize; |
| 33 | 0 | _cursor.batchSize(_scope_options.batchSize, callback); |
| 34 | 0 | return this; |
| 35 | } | |
| 36 | ||
| 37 | 0 | this.count = function(applySkipLimit, callback) { |
| 38 | 0 | return _cursor.count(applySkipLimit, callback); |
| 39 | } | |
| 40 | ||
| 41 | 0 | this.stream = function(options) { |
| 42 | 0 | return _cursor.stream(options); |
| 43 | } | |
| 44 | ||
| 45 | 0 | this.close = function(callback) { |
| 46 | 0 | return _cursor.close(callback); |
| 47 | } | |
| 48 | ||
| 49 | 0 | this.explain = function(callback) { |
| 50 | 0 | return _cursor.explain(callback); |
| 51 | } | |
| 52 | ||
| 53 | 0 | this.isClosed = function(callback) { |
| 54 | 0 | return _cursor.isClosed(); |
| 55 | } | |
| 56 | ||
| 57 | 0 | this.rewind = function() { |
| 58 | 0 | return _cursor.rewind(); |
| 59 | } | |
| 60 | ||
| 61 | // Internal methods | |
| 62 | 0 | this.limit = function(limit, callback) { |
| 63 | 0 | _cursor.limit(limit, callback); |
| 64 | 0 | _scope_options.limit = limit; |
| 65 | 0 | return this; |
| 66 | } | |
| 67 | ||
| 68 | 0 | this.skip = function(skip, callback) { |
| 69 | 0 | _cursor.skip(skip, callback); |
| 70 | 0 | _scope_options.skip = skip; |
| 71 | 0 | return this; |
| 72 | } | |
| 73 | ||
| 74 | 0 | this.hint = function(hint) { |
| 75 | 0 | _scope_options.hint = hint; |
| 76 | 0 | _cursor.hint = _scope_options.hint; |
| 77 | 0 | return this; |
| 78 | } | |
| 79 | ||
| 80 | 0 | this.maxTimeMS = function(maxTimeMS) { |
| 81 | 0 | _cursor.maxTimeMS(maxTimeMS) |
| 82 | 0 | _scope_options.maxTimeMS = maxTimeMS; |
| 83 | 0 | return this; |
| 84 | }, | |
| 85 | ||
| 86 | this.sort = function(keyOrList, direction, callback) { | |
| 87 | 0 | _cursor.sort(keyOrList, direction, callback); |
| 88 | 0 | _scope_options.sort = keyOrList; |
| 89 | 0 | return this; |
| 90 | }, | |
| 91 | ||
| 92 | this.fields = function(fields) { | |
| 93 | 0 | _fields = fields; |
| 94 | 0 | _cursor.fields = _fields; |
| 95 | 0 | return this; |
| 96 | } | |
| 97 | ||
| 98 | // | |
| 99 | // Backward compatible settings | |
| 100 | 0 | Object.defineProperty(this, "timeout", { |
| 101 | get: function() { | |
| 102 | 0 | return _cursor.timeout; |
| 103 | } | |
| 104 | }); | |
| 105 | ||
| 106 | 0 | Object.defineProperty(this, "read", { |
| 107 | get: function() { | |
| 108 | 0 | return _cursor.read; |
| 109 | } | |
| 110 | }); | |
| 111 | ||
| 112 | 0 | Object.defineProperty(this, "items", { |
| 113 | get: function() { | |
| 114 | 0 | return _cursor.items; |
| 115 | } | |
| 116 | }); | |
| 117 | } | |
| 118 | ||
| 119 | 1 | var Scope = function(collection, _selector, _fields, _scope_options) { |
| 120 | 0 | var self = this; |
| 121 | ||
| 122 | // Ensure we have at least an empty cursor options object | |
| 123 | 0 | _scope_options = _scope_options || {}; |
| 124 | 0 | var _write_concern = _scope_options.write_concern || null; |
| 125 | ||
| 126 | // Ensure default read preference | |
| 127 | 0 | if(!_scope_options.readPreference) _scope_options.readPreference = {readPreference: 'primary'}; |
| 128 | ||
| 129 | // Set up the cursor | |
| 130 | 0 | var _cursor = new Cursor2( |
| 131 | collection.db, collection, _selector | |
| 132 | , _fields, _scope_options | |
| 133 | ); | |
| 134 | ||
| 135 | // Write branch options | |
| 136 | 0 | var writeOptions = { |
| 137 | insert: function(documents, callback) { | |
| 138 | // Merge together options | |
| 139 | 0 | var options = _write_concern || {}; |
| 140 | // Execute insert | |
| 141 | 0 | collection.insert(documents, options, callback); |
| 142 | }, | |
| 143 | ||
| 144 | save: function(document, callback) { | |
| 145 | // Merge together options | |
| 146 | 0 | var save_options = _write_concern || {}; |
| 147 | // Execute save | |
| 148 | 0 | collection.save(document, save_options, function(err, result) { |
| 149 | 0 | if(typeof result == 'number' && result == 1) { |
| 150 | 0 | return callback(null, document); |
| 151 | } | |
| 152 | ||
| 153 | 0 | return callback(null, document); |
| 154 | }); | |
| 155 | }, | |
| 156 | ||
| 157 | find: function(selector) { | |
| 158 | 0 | _selector = selector; |
| 159 | 0 | return writeOptions; |
| 160 | }, | |
| 161 | ||
| 162 | // | |
| 163 | // Update is implicit multiple document update | |
| 164 | update: function(operations, callback) { | |
| 165 | // Merge together options | |
| 166 | 0 | var update_options = _write_concern || {}; |
| 167 | ||
| 168 | // Set up options, multi is default operation | |
| 169 | 0 | update_options.multi = _scope_options.multi ? _scope_options.multi : true; |
| 170 | 0 | if(_scope_options.upsert) update_options.upsert = _scope_options.upsert; |
| 171 | ||
| 172 | // Execute options | |
| 173 | 0 | collection.update(_selector, operations, update_options, function(err, result, obj) { |
| 174 | 0 | callback(err, obj); |
| 175 | }); | |
| 176 | }, | |
| 177 | } | |
| 178 | ||
| 179 | // Set write concern | |
| 180 | 0 | this.withWriteConcern = function(write_concern) { |
| 181 | // Save the current write concern to the Scope | |
| 182 | 0 | _scope_options.write_concern = write_concern; |
| 183 | 0 | _write_concern = write_concern; |
| 184 | // Only allow legal options | |
| 185 | 0 | return writeOptions; |
| 186 | } | |
| 187 | ||
| 188 | // Start find | |
| 189 | 0 | this.find = function(selector, options) { |
| 190 | // Save the current selector | |
| 191 | 0 | _selector = selector; |
| 192 | // Set the cursor | |
| 193 | 0 | _cursor.selector = selector; |
| 194 | // Return only legal read options | |
| 195 | 0 | return new Cursor(_scope_options, _cursor); |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | 1 | exports.Scope = Scope; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var timers = require('timers'); |
| 2 | ||
| 3 | /** | |
| 4 | * Sort functions, Normalize and prepare sort parameters | |
| 5 | */ | |
| 6 | 1 | var formatSortValue = exports.formatSortValue = function(sortDirection) { |
| 7 | 0 | var value = ("" + sortDirection).toLowerCase(); |
| 8 | ||
| 9 | 0 | switch (value) { |
| 10 | case 'ascending': | |
| 11 | case 'asc': | |
| 12 | case '1': | |
| 13 | 0 | return 1; |
| 14 | case 'descending': | |
| 15 | case 'desc': | |
| 16 | case '-1': | |
| 17 | 0 | return -1; |
| 18 | default: | |
| 19 | 0 | throw new Error("Illegal sort clause, must be of the form " |
| 20 | + "[['field1', '(ascending|descending)'], " | |
| 21 | + "['field2', '(ascending|descending)']]"); | |
| 22 | } | |
| 23 | }; | |
| 24 | ||
| 25 | 1 | var formattedOrderClause = exports.formattedOrderClause = function(sortValue) { |
| 26 | 0 | var orderBy = {}; |
| 27 | ||
| 28 | 0 | if (Array.isArray(sortValue)) { |
| 29 | 0 | for(var i = 0; i < sortValue.length; i++) { |
| 30 | 0 | if(sortValue[i].constructor == String) { |
| 31 | 0 | orderBy[sortValue[i]] = 1; |
| 32 | } else { | |
| 33 | 0 | orderBy[sortValue[i][0]] = formatSortValue(sortValue[i][1]); |
| 34 | } | |
| 35 | } | |
| 36 | 0 | } else if(Object.prototype.toString.call(sortValue) === '[object Object]') { |
| 37 | 0 | orderBy = sortValue; |
| 38 | 0 | } else if (sortValue.constructor == String) { |
| 39 | 0 | orderBy[sortValue] = 1; |
| 40 | } else { | |
| 41 | 0 | throw new Error("Illegal sort clause, must be of the form " + |
| 42 | "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"); | |
| 43 | } | |
| 44 | ||
| 45 | 0 | return orderBy; |
| 46 | }; | |
| 47 | ||
| 48 | 1 | exports.encodeInt = function(value) { |
| 49 | 0 | var buffer = new Buffer(4); |
| 50 | 0 | buffer[3] = (value >> 24) & 0xff; |
| 51 | 0 | buffer[2] = (value >> 16) & 0xff; |
| 52 | 0 | buffer[1] = (value >> 8) & 0xff; |
| 53 | 0 | buffer[0] = value & 0xff; |
| 54 | 0 | return buffer; |
| 55 | } | |
| 56 | ||
| 57 | 1 | exports.encodeIntInPlace = function(value, buffer, index) { |
| 58 | 0 | buffer[index + 3] = (value >> 24) & 0xff; |
| 59 | 0 | buffer[index + 2] = (value >> 16) & 0xff; |
| 60 | 0 | buffer[index + 1] = (value >> 8) & 0xff; |
| 61 | 0 | buffer[index] = value & 0xff; |
| 62 | } | |
| 63 | ||
| 64 | 1 | exports.encodeCString = function(string) { |
| 65 | 0 | var buf = new Buffer(string, 'utf8'); |
| 66 | 0 | return [buf, new Buffer([0])]; |
| 67 | } | |
| 68 | ||
| 69 | 1 | exports.decodeUInt32 = function(array, index) { |
| 70 | 0 | return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24; |
| 71 | } | |
| 72 | ||
| 73 | // Decode the int | |
| 74 | 1 | exports.decodeUInt8 = function(array, index) { |
| 75 | 0 | return array[index]; |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * Context insensitive type checks | |
| 80 | */ | |
| 81 | ||
| 82 | 1 | var toString = Object.prototype.toString; |
| 83 | ||
| 84 | 1 | exports.isObject = function (arg) { |
| 85 | 0 | return '[object Object]' == toString.call(arg) |
| 86 | } | |
| 87 | ||
| 88 | 1 | exports.isArray = function (arg) { |
| 89 | 0 | return Array.isArray(arg) || |
| 90 | 'object' == typeof arg && '[object Array]' == toString.call(arg) | |
| 91 | } | |
| 92 | ||
| 93 | 1 | exports.isDate = function (arg) { |
| 94 | 0 | return 'object' == typeof arg && '[object Date]' == toString.call(arg) |
| 95 | } | |
| 96 | ||
| 97 | 1 | exports.isRegExp = function (arg) { |
| 98 | 0 | return 'object' == typeof arg && '[object RegExp]' == toString.call(arg) |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Wrap a Mongo error document in an Error instance | |
| 103 | * @ignore | |
| 104 | * @api private | |
| 105 | */ | |
| 106 | 1 | var toError = function(error) { |
| 107 | 0 | if (error instanceof Error) return error; |
| 108 | ||
| 109 | 0 | var msg = error.err || error.errmsg || error.errMessage || error; |
| 110 | 0 | var e = new Error(msg); |
| 111 | 0 | e.name = 'MongoError'; |
| 112 | ||
| 113 | // Get all object keys | |
| 114 | 0 | var keys = typeof error == 'object' |
| 115 | ? Object.keys(error) | |
| 116 | : []; | |
| 117 | ||
| 118 | 0 | for(var i = 0; i < keys.length; i++) { |
| 119 | 0 | e[keys[i]] = error[keys[i]]; |
| 120 | } | |
| 121 | ||
| 122 | 0 | return e; |
| 123 | } | |
| 124 | 1 | exports.toError = toError; |
| 125 | ||
| 126 | /** | |
| 127 | * Convert a single level object to an array | |
| 128 | * @ignore | |
| 129 | * @api private | |
| 130 | */ | |
| 131 | 1 | exports.objectToArray = function(object) { |
| 132 | 0 | var list = []; |
| 133 | ||
| 134 | 0 | for(var name in object) { |
| 135 | 0 | list.push(object[name]) |
| 136 | } | |
| 137 | ||
| 138 | 0 | return list; |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Handle single command document return | |
| 143 | * @ignore | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | 1 | exports.handleSingleCommandResultReturn = function(override_value_true, override_value_false, callback) { |
| 147 | 0 | return function(err, result, connection) { |
| 148 | 0 | if(err && typeof callback == 'function') return callback(err, null); |
| 149 | 0 | if(!result || !result.documents || result.documents.length == 0) |
| 150 | 0 | if(typeof callback == 'function') return callback(toError("command failed to return results"), null) |
| 151 | 0 | if(result && result.documents[0].ok == 1) { |
| 152 | 0 | if(override_value_true) return callback(null, override_value_true) |
| 153 | 0 | if(typeof callback == 'function') return callback(null, result.documents[0]); |
| 154 | } | |
| 155 | ||
| 156 | // Return the error from the document | |
| 157 | 0 | if(typeof callback == 'function') return callback(toError(result.documents[0]), override_value_false); |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * Return correct processor | |
| 163 | * @ignore | |
| 164 | * @api private | |
| 165 | */ | |
| 166 | 1 | exports.processor = function() { |
| 167 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 168 | 10 | process.maxTickDepth = Infinity; |
| 169 | // Only use nextTick | |
| 170 | 10 | return process.nextTick; |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * Allow setting the socketTimeoutMS on all connections | |
| 175 | * to work around issues such as secondaries blocking due to compaction | |
| 176 | * | |
| 177 | * @ignore | |
| 178 | * @api private | |
| 179 | */ | |
| 180 | 1 | exports.setSocketTimeoutProperty = function(self, options) { |
| 181 | 0 | Object.defineProperty(self, "socketTimeoutMS", { |
| 182 | enumerable: true | |
| 183 | 0 | , get: function () { return options.socketTimeoutMS; } |
| 184 | , set: function (value) { | |
| 185 | // Set the socket timeoutMS value | |
| 186 | 0 | options.socketTimeoutMS = value; |
| 187 | ||
| 188 | // Get all the connections | |
| 189 | 0 | var connections = self.allRawConnections(); |
| 190 | 0 | for(var i = 0; i < connections.length; i++) { |
| 191 | 0 | connections[i].socketTimeoutMS = value; |
| 192 | } | |
| 193 | } | |
| 194 | }); | |
| 195 | } | |
| 196 | ||
| 197 | 1 | exports.hasWriteCommands = function(connection) { |
| 198 | 0 | return connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasWriteCommands; |
| 199 | } | |
| 200 | ||
| 201 | ||
| 202 | ||
| 203 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | if(typeof window === 'undefined') { |
| 5 | 1 | var Buffer = require('buffer').Buffer; // TODO just use global Buffer |
| 6 | } | |
| 7 | ||
| 8 | // Binary default subtype | |
| 9 | 1 | var BSON_BINARY_SUBTYPE_DEFAULT = 0; |
| 10 | ||
| 11 | /** | |
| 12 | * @ignore | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | 1 | var writeStringToArray = function(data) { |
| 16 | // Create a buffer | |
| 17 | 0 | var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(data.length)) : new Array(data.length); |
| 18 | // Write the content to the buffer | |
| 19 | 0 | for(var i = 0; i < data.length; i++) { |
| 20 | 0 | buffer[i] = data.charCodeAt(i); |
| 21 | } | |
| 22 | // Write the string to the buffer | |
| 23 | 0 | return buffer; |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Convert Array ot Uint8Array to Binary String | |
| 28 | * | |
| 29 | * @ignore | |
| 30 | * @api private | |
| 31 | */ | |
| 32 | 1 | var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { |
| 33 | 0 | var result = ""; |
| 34 | 0 | for(var i = startIndex; i < endIndex; i++) { |
| 35 | 0 | result = result + String.fromCharCode(byteArray[i]); |
| 36 | } | |
| 37 | 0 | return result; |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * A class representation of the BSON Binary type. | |
| 42 | * | |
| 43 | * Sub types | |
| 44 | * - **BSON.BSON_BINARY_SUBTYPE_DEFAULT**, default BSON type. | |
| 45 | * - **BSON.BSON_BINARY_SUBTYPE_FUNCTION**, BSON function type. | |
| 46 | * - **BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY**, BSON byte array type. | |
| 47 | * - **BSON.BSON_BINARY_SUBTYPE_UUID**, BSON uuid type. | |
| 48 | * - **BSON.BSON_BINARY_SUBTYPE_MD5**, BSON md5 type. | |
| 49 | * - **BSON.BSON_BINARY_SUBTYPE_USER_DEFINED**, BSON user defined type. | |
| 50 | * | |
| 51 | * @class Represents the Binary BSON type. | |
| 52 | * @param {Buffer} buffer a buffer object containing the binary data. | |
| 53 | * @param {Number} [subType] the option binary type. | |
| 54 | * @return {Grid} | |
| 55 | */ | |
| 56 | 1 | function Binary(buffer, subType) { |
| 57 | 0 | if(!(this instanceof Binary)) return new Binary(buffer, subType); |
| 58 | ||
| 59 | 0 | this._bsontype = 'Binary'; |
| 60 | ||
| 61 | 0 | if(buffer instanceof Number) { |
| 62 | 0 | this.sub_type = buffer; |
| 63 | 0 | this.position = 0; |
| 64 | } else { | |
| 65 | 0 | this.sub_type = subType == null ? BSON_BINARY_SUBTYPE_DEFAULT : subType; |
| 66 | 0 | this.position = 0; |
| 67 | } | |
| 68 | ||
| 69 | 0 | if(buffer != null && !(buffer instanceof Number)) { |
| 70 | // Only accept Buffer, Uint8Array or Arrays | |
| 71 | 0 | if(typeof buffer == 'string') { |
| 72 | // Different ways of writing the length of the string for the different types | |
| 73 | 0 | if(typeof Buffer != 'undefined') { |
| 74 | 0 | this.buffer = new Buffer(buffer); |
| 75 | 0 | } else if(typeof Uint8Array != 'undefined' || (Object.prototype.toString.call(buffer) == '[object Array]')) { |
| 76 | 0 | this.buffer = writeStringToArray(buffer); |
| 77 | } else { | |
| 78 | 0 | throw new Error("only String, Buffer, Uint8Array or Array accepted"); |
| 79 | } | |
| 80 | } else { | |
| 81 | 0 | this.buffer = buffer; |
| 82 | } | |
| 83 | 0 | this.position = buffer.length; |
| 84 | } else { | |
| 85 | 0 | if(typeof Buffer != 'undefined') { |
| 86 | 0 | this.buffer = new Buffer(Binary.BUFFER_SIZE); |
| 87 | 0 | } else if(typeof Uint8Array != 'undefined'){ |
| 88 | 0 | this.buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE)); |
| 89 | } else { | |
| 90 | 0 | this.buffer = new Array(Binary.BUFFER_SIZE); |
| 91 | } | |
| 92 | // Set position to start of buffer | |
| 93 | 0 | this.position = 0; |
| 94 | } | |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Updates this binary with byte_value. | |
| 99 | * | |
| 100 | * @param {Character} byte_value a single byte we wish to write. | |
| 101 | * @api public | |
| 102 | */ | |
| 103 | 1 | Binary.prototype.put = function put(byte_value) { |
| 104 | // If it's a string and a has more than one character throw an error | |
| 105 | 0 | if(byte_value['length'] != null && typeof byte_value != 'number' && byte_value.length != 1) throw new Error("only accepts single character String, Uint8Array or Array"); |
| 106 | 0 | if(typeof byte_value != 'number' && byte_value < 0 || byte_value > 255) throw new Error("only accepts number in a valid unsigned byte range 0-255"); |
| 107 | ||
| 108 | // Decode the byte value once | |
| 109 | 0 | var decoded_byte = null; |
| 110 | 0 | if(typeof byte_value == 'string') { |
| 111 | 0 | decoded_byte = byte_value.charCodeAt(0); |
| 112 | 0 | } else if(byte_value['length'] != null) { |
| 113 | 0 | decoded_byte = byte_value[0]; |
| 114 | } else { | |
| 115 | 0 | decoded_byte = byte_value; |
| 116 | } | |
| 117 | ||
| 118 | 0 | if(this.buffer.length > this.position) { |
| 119 | 0 | this.buffer[this.position++] = decoded_byte; |
| 120 | } else { | |
| 121 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 122 | // Create additional overflow buffer | |
| 123 | 0 | var buffer = new Buffer(Binary.BUFFER_SIZE + this.buffer.length); |
| 124 | // Combine the two buffers together | |
| 125 | 0 | this.buffer.copy(buffer, 0, 0, this.buffer.length); |
| 126 | 0 | this.buffer = buffer; |
| 127 | 0 | this.buffer[this.position++] = decoded_byte; |
| 128 | } else { | |
| 129 | 0 | var buffer = null; |
| 130 | // Create a new buffer (typed or normal array) | |
| 131 | 0 | if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { |
| 132 | 0 | buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE + this.buffer.length)); |
| 133 | } else { | |
| 134 | 0 | buffer = new Array(Binary.BUFFER_SIZE + this.buffer.length); |
| 135 | } | |
| 136 | ||
| 137 | // We need to copy all the content to the new array | |
| 138 | 0 | for(var i = 0; i < this.buffer.length; i++) { |
| 139 | 0 | buffer[i] = this.buffer[i]; |
| 140 | } | |
| 141 | ||
| 142 | // Reassign the buffer | |
| 143 | 0 | this.buffer = buffer; |
| 144 | // Write the byte | |
| 145 | 0 | this.buffer[this.position++] = decoded_byte; |
| 146 | } | |
| 147 | } | |
| 148 | }; | |
| 149 | ||
| 150 | /** | |
| 151 | * Writes a buffer or string to the binary. | |
| 152 | * | |
| 153 | * @param {Buffer|String} string a string or buffer to be written to the Binary BSON object. | |
| 154 | * @param {Number} offset specify the binary of where to write the content. | |
| 155 | * @api public | |
| 156 | */ | |
| 157 | 1 | Binary.prototype.write = function write(string, offset) { |
| 158 | 0 | offset = typeof offset == 'number' ? offset : this.position; |
| 159 | ||
| 160 | // If the buffer is to small let's extend the buffer | |
| 161 | 0 | if(this.buffer.length < offset + string.length) { |
| 162 | 0 | var buffer = null; |
| 163 | // If we are in node.js | |
| 164 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 165 | 0 | buffer = new Buffer(this.buffer.length + string.length); |
| 166 | 0 | this.buffer.copy(buffer, 0, 0, this.buffer.length); |
| 167 | 0 | } else if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { |
| 168 | // Create a new buffer | |
| 169 | 0 | buffer = new Uint8Array(new ArrayBuffer(this.buffer.length + string.length)) |
| 170 | // Copy the content | |
| 171 | 0 | for(var i = 0; i < this.position; i++) { |
| 172 | 0 | buffer[i] = this.buffer[i]; |
| 173 | } | |
| 174 | } | |
| 175 | ||
| 176 | // Assign the new buffer | |
| 177 | 0 | this.buffer = buffer; |
| 178 | } | |
| 179 | ||
| 180 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(string) && Buffer.isBuffer(this.buffer)) { |
| 181 | 0 | string.copy(this.buffer, offset, 0, string.length); |
| 182 | 0 | this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; |
| 183 | // offset = string.length | |
| 184 | 0 | } else if(typeof Buffer != 'undefined' && typeof string == 'string' && Buffer.isBuffer(this.buffer)) { |
| 185 | 0 | this.buffer.write(string, 'binary', offset); |
| 186 | 0 | this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; |
| 187 | // offset = string.length; | |
| 188 | 0 | } else if(Object.prototype.toString.call(string) == '[object Uint8Array]' |
| 189 | || Object.prototype.toString.call(string) == '[object Array]' && typeof string != 'string') { | |
| 190 | 0 | for(var i = 0; i < string.length; i++) { |
| 191 | 0 | this.buffer[offset++] = string[i]; |
| 192 | } | |
| 193 | ||
| 194 | 0 | this.position = offset > this.position ? offset : this.position; |
| 195 | 0 | } else if(typeof string == 'string') { |
| 196 | 0 | for(var i = 0; i < string.length; i++) { |
| 197 | 0 | this.buffer[offset++] = string.charCodeAt(i); |
| 198 | } | |
| 199 | ||
| 200 | 0 | this.position = offset > this.position ? offset : this.position; |
| 201 | } | |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * Reads **length** bytes starting at **position**. | |
| 206 | * | |
| 207 | * @param {Number} position read from the given position in the Binary. | |
| 208 | * @param {Number} length the number of bytes to read. | |
| 209 | * @return {Buffer} | |
| 210 | * @api public | |
| 211 | */ | |
| 212 | 1 | Binary.prototype.read = function read(position, length) { |
| 213 | 0 | length = length && length > 0 |
| 214 | ? length | |
| 215 | : this.position; | |
| 216 | ||
| 217 | // Let's return the data based on the type we have | |
| 218 | 0 | if(this.buffer['slice']) { |
| 219 | 0 | return this.buffer.slice(position, position + length); |
| 220 | } else { | |
| 221 | // Create a buffer to keep the result | |
| 222 | 0 | var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(length)) : new Array(length); |
| 223 | 0 | for(var i = 0; i < length; i++) { |
| 224 | 0 | buffer[i] = this.buffer[position++]; |
| 225 | } | |
| 226 | } | |
| 227 | // Return the buffer | |
| 228 | 0 | return buffer; |
| 229 | }; | |
| 230 | ||
| 231 | /** | |
| 232 | * Returns the value of this binary as a string. | |
| 233 | * | |
| 234 | * @return {String} | |
| 235 | * @api public | |
| 236 | */ | |
| 237 | 1 | Binary.prototype.value = function value(asRaw) { |
| 238 | 0 | asRaw = asRaw == null ? false : asRaw; |
| 239 | ||
| 240 | // If it's a node.js buffer object | |
| 241 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 242 | 0 | return asRaw ? this.buffer.slice(0, this.position) : this.buffer.toString('binary', 0, this.position); |
| 243 | } else { | |
| 244 | 0 | if(asRaw) { |
| 245 | // we support the slice command use it | |
| 246 | 0 | if(this.buffer['slice'] != null) { |
| 247 | 0 | return this.buffer.slice(0, this.position); |
| 248 | } else { | |
| 249 | // Create a new buffer to copy content to | |
| 250 | 0 | var newBuffer = Object.prototype.toString.call(this.buffer) == '[object Uint8Array]' ? new Uint8Array(new ArrayBuffer(this.position)) : new Array(this.position); |
| 251 | // Copy content | |
| 252 | 0 | for(var i = 0; i < this.position; i++) { |
| 253 | 0 | newBuffer[i] = this.buffer[i]; |
| 254 | } | |
| 255 | // Return the buffer | |
| 256 | 0 | return newBuffer; |
| 257 | } | |
| 258 | } else { | |
| 259 | 0 | return convertArraytoUtf8BinaryString(this.buffer, 0, this.position); |
| 260 | } | |
| 261 | } | |
| 262 | }; | |
| 263 | ||
| 264 | /** | |
| 265 | * Length. | |
| 266 | * | |
| 267 | * @return {Number} the length of the binary. | |
| 268 | * @api public | |
| 269 | */ | |
| 270 | 1 | Binary.prototype.length = function length() { |
| 271 | 0 | return this.position; |
| 272 | }; | |
| 273 | ||
| 274 | /** | |
| 275 | * @ignore | |
| 276 | * @api private | |
| 277 | */ | |
| 278 | 1 | Binary.prototype.toJSON = function() { |
| 279 | 0 | return this.buffer != null ? this.buffer.toString('base64') : ''; |
| 280 | } | |
| 281 | ||
| 282 | /** | |
| 283 | * @ignore | |
| 284 | * @api private | |
| 285 | */ | |
| 286 | 1 | Binary.prototype.toString = function(format) { |
| 287 | 0 | return this.buffer != null ? this.buffer.slice(0, this.position).toString(format) : ''; |
| 288 | } | |
| 289 | ||
| 290 | 1 | Binary.BUFFER_SIZE = 256; |
| 291 | ||
| 292 | /** | |
| 293 | * Default BSON type | |
| 294 | * | |
| 295 | * @classconstant SUBTYPE_DEFAULT | |
| 296 | **/ | |
| 297 | 1 | Binary.SUBTYPE_DEFAULT = 0; |
| 298 | /** | |
| 299 | * Function BSON type | |
| 300 | * | |
| 301 | * @classconstant SUBTYPE_DEFAULT | |
| 302 | **/ | |
| 303 | 1 | Binary.SUBTYPE_FUNCTION = 1; |
| 304 | /** | |
| 305 | * Byte Array BSON type | |
| 306 | * | |
| 307 | * @classconstant SUBTYPE_DEFAULT | |
| 308 | **/ | |
| 309 | 1 | Binary.SUBTYPE_BYTE_ARRAY = 2; |
| 310 | /** | |
| 311 | * OLD UUID BSON type | |
| 312 | * | |
| 313 | * @classconstant SUBTYPE_DEFAULT | |
| 314 | **/ | |
| 315 | 1 | Binary.SUBTYPE_UUID_OLD = 3; |
| 316 | /** | |
| 317 | * UUID BSON type | |
| 318 | * | |
| 319 | * @classconstant SUBTYPE_DEFAULT | |
| 320 | **/ | |
| 321 | 1 | Binary.SUBTYPE_UUID = 4; |
| 322 | /** | |
| 323 | * MD5 BSON type | |
| 324 | * | |
| 325 | * @classconstant SUBTYPE_DEFAULT | |
| 326 | **/ | |
| 327 | 1 | Binary.SUBTYPE_MD5 = 5; |
| 328 | /** | |
| 329 | * User BSON type | |
| 330 | * | |
| 331 | * @classconstant SUBTYPE_DEFAULT | |
| 332 | **/ | |
| 333 | 1 | Binary.SUBTYPE_USER_DEFINED = 128; |
| 334 | ||
| 335 | /** | |
| 336 | * Expose. | |
| 337 | */ | |
| 338 | 1 | exports.Binary = Binary; |
| 339 | ||
| 340 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Binary Parser. | |
| 3 | * Jonas Raoni Soares Silva | |
| 4 | * http://jsfromhell.com/classes/binary-parser [v1.0] | |
| 5 | */ | |
| 6 | 1 | var chr = String.fromCharCode; |
| 7 | ||
| 8 | 1 | var maxBits = []; |
| 9 | 1 | for (var i = 0; i < 64; i++) { |
| 10 | 64 | maxBits[i] = Math.pow(2, i); |
| 11 | } | |
| 12 | ||
| 13 | 1 | function BinaryParser (bigEndian, allowExceptions) { |
| 14 | 0 | if(!(this instanceof BinaryParser)) return new BinaryParser(bigEndian, allowExceptions); |
| 15 | ||
| 16 | 0 | this.bigEndian = bigEndian; |
| 17 | 0 | this.allowExceptions = allowExceptions; |
| 18 | }; | |
| 19 | ||
| 20 | 1 | BinaryParser.warn = function warn (msg) { |
| 21 | 0 | if (this.allowExceptions) { |
| 22 | 0 | throw new Error(msg); |
| 23 | } | |
| 24 | ||
| 25 | 0 | return 1; |
| 26 | }; | |
| 27 | ||
| 28 | 1 | BinaryParser.decodeFloat = function decodeFloat (data, precisionBits, exponentBits) { |
| 29 | 0 | var b = new this.Buffer(this.bigEndian, data); |
| 30 | ||
| 31 | 0 | b.checkBuffer(precisionBits + exponentBits + 1); |
| 32 | ||
| 33 | 0 | var bias = maxBits[exponentBits - 1] - 1 |
| 34 | , signal = b.readBits(precisionBits + exponentBits, 1) | |
| 35 | , exponent = b.readBits(precisionBits, exponentBits) | |
| 36 | , significand = 0 | |
| 37 | , divisor = 2 | |
| 38 | , curByte = b.buffer.length + (-precisionBits >> 3) - 1; | |
| 39 | ||
| 40 | 0 | do { |
| 41 | 0 | for (var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); |
| 42 | } while (precisionBits -= startBit); | |
| 43 | ||
| 44 | 0 | return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 ); |
| 45 | }; | |
| 46 | ||
| 47 | 1 | BinaryParser.decodeInt = function decodeInt (data, bits, signed, forceBigEndian) { |
| 48 | 0 | var b = new this.Buffer(this.bigEndian || forceBigEndian, data) |
| 49 | , x = b.readBits(0, bits) | |
| 50 | , max = maxBits[bits]; //max = Math.pow( 2, bits ); | |
| 51 | ||
| 52 | 0 | return signed && x >= max / 2 |
| 53 | ? x - max | |
| 54 | : x; | |
| 55 | }; | |
| 56 | ||
| 57 | 1 | BinaryParser.encodeFloat = function encodeFloat (data, precisionBits, exponentBits) { |
| 58 | 0 | var bias = maxBits[exponentBits - 1] - 1 |
| 59 | , minExp = -bias + 1 | |
| 60 | , maxExp = bias | |
| 61 | , minUnnormExp = minExp - precisionBits | |
| 62 | , n = parseFloat(data) | |
| 63 | , status = isNaN(n) || n == -Infinity || n == +Infinity ? n : 0 | |
| 64 | , exp = 0 | |
| 65 | , len = 2 * bias + 1 + precisionBits + 3 | |
| 66 | , bin = new Array(len) | |
| 67 | , signal = (n = status !== 0 ? 0 : n) < 0 | |
| 68 | , intPart = Math.floor(n = Math.abs(n)) | |
| 69 | , floatPart = n - intPart | |
| 70 | , lastBit | |
| 71 | , rounded | |
| 72 | , result | |
| 73 | , i | |
| 74 | , j; | |
| 75 | ||
| 76 | 0 | for (i = len; i; bin[--i] = 0); |
| 77 | ||
| 78 | 0 | for (i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2)); |
| 79 | ||
| 80 | 0 | for (i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0 ) && --floatPart); |
| 81 | ||
| 82 | 0 | for (i = -1; ++i < len && !bin[i];); |
| 83 | ||
| 84 | 0 | if (bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]) { |
| 85 | 0 | if (!(rounded = bin[lastBit])) { |
| 86 | 0 | for (j = lastBit + 2; !rounded && j < len; rounded = bin[j++]); |
| 87 | } | |
| 88 | ||
| 89 | 0 | for (j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0)); |
| 90 | } | |
| 91 | ||
| 92 | 0 | for (i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];); |
| 93 | ||
| 94 | 0 | if ((exp = bias + 1 - i) >= minExp && exp <= maxExp) { |
| 95 | 0 | ++i; |
| 96 | 0 | } else if (exp < minExp) { |
| 97 | 0 | exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow"); |
| 98 | 0 | i = bias + 1 - (exp = minExp - 1); |
| 99 | } | |
| 100 | ||
| 101 | 0 | if (intPart || status !== 0) { |
| 102 | 0 | this.warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status); |
| 103 | 0 | exp = maxExp + 1; |
| 104 | 0 | i = bias + 2; |
| 105 | ||
| 106 | 0 | if (status == -Infinity) { |
| 107 | 0 | signal = 1; |
| 108 | 0 | } else if (isNaN(status)) { |
| 109 | 0 | bin[i] = 1; |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | 0 | for (n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1); |
| 114 | ||
| 115 | 0 | for (n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = []; i; j = (j + 1) % 8) { |
| 116 | 0 | n += (1 << j) * result.charAt(--i); |
| 117 | 0 | if (j == 7) { |
| 118 | 0 | r[r.length] = String.fromCharCode(n); |
| 119 | 0 | n = 0; |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | 0 | r[r.length] = n |
| 124 | ? String.fromCharCode(n) | |
| 125 | : ""; | |
| 126 | ||
| 127 | 0 | return (this.bigEndian ? r.reverse() : r).join(""); |
| 128 | }; | |
| 129 | ||
| 130 | 1 | BinaryParser.encodeInt = function encodeInt (data, bits, signed, forceBigEndian) { |
| 131 | 0 | var max = maxBits[bits]; |
| 132 | ||
| 133 | 0 | if (data >= max || data < -(max / 2)) { |
| 134 | 0 | this.warn("encodeInt::overflow"); |
| 135 | 0 | data = 0; |
| 136 | } | |
| 137 | ||
| 138 | 0 | if (data < 0) { |
| 139 | 0 | data += max; |
| 140 | } | |
| 141 | ||
| 142 | 0 | for (var r = []; data; r[r.length] = String.fromCharCode(data % 256), data = Math.floor(data / 256)); |
| 143 | ||
| 144 | 0 | for (bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0"); |
| 145 | ||
| 146 | 0 | return ((this.bigEndian || forceBigEndian) ? r.reverse() : r).join(""); |
| 147 | }; | |
| 148 | ||
| 149 | 1 | BinaryParser.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; |
| 150 | 1 | BinaryParser.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; |
| 151 | 1 | BinaryParser.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; |
| 152 | 1 | BinaryParser.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; |
| 153 | 1 | BinaryParser.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; |
| 154 | 1 | BinaryParser.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; |
| 155 | 1 | BinaryParser.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; |
| 156 | 1 | BinaryParser.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; |
| 157 | 1 | BinaryParser.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; |
| 158 | 1 | BinaryParser.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; |
| 159 | 1 | BinaryParser.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; |
| 160 | 1 | BinaryParser.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; |
| 161 | 1 | BinaryParser.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; |
| 162 | 1 | BinaryParser.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; |
| 163 | 1 | BinaryParser.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; |
| 164 | 1 | BinaryParser.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; |
| 165 | 1 | BinaryParser.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; |
| 166 | 1 | BinaryParser.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; |
| 167 | 1 | BinaryParser.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; |
| 168 | 1 | BinaryParser.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; |
| 169 | ||
| 170 | // Factor out the encode so it can be shared by add_header and push_int32 | |
| 171 | 1 | BinaryParser.encode_int32 = function encode_int32 (number, asArray) { |
| 172 | 0 | var a, b, c, d, unsigned; |
| 173 | 0 | unsigned = (number < 0) ? (number + 0x100000000) : number; |
| 174 | 0 | a = Math.floor(unsigned / 0xffffff); |
| 175 | 0 | unsigned &= 0xffffff; |
| 176 | 0 | b = Math.floor(unsigned / 0xffff); |
| 177 | 0 | unsigned &= 0xffff; |
| 178 | 0 | c = Math.floor(unsigned / 0xff); |
| 179 | 0 | unsigned &= 0xff; |
| 180 | 0 | d = Math.floor(unsigned); |
| 181 | 0 | return asArray ? [chr(a), chr(b), chr(c), chr(d)] : chr(a) + chr(b) + chr(c) + chr(d); |
| 182 | }; | |
| 183 | ||
| 184 | 1 | BinaryParser.encode_int64 = function encode_int64 (number) { |
| 185 | 0 | var a, b, c, d, e, f, g, h, unsigned; |
| 186 | 0 | unsigned = (number < 0) ? (number + 0x10000000000000000) : number; |
| 187 | 0 | a = Math.floor(unsigned / 0xffffffffffffff); |
| 188 | 0 | unsigned &= 0xffffffffffffff; |
| 189 | 0 | b = Math.floor(unsigned / 0xffffffffffff); |
| 190 | 0 | unsigned &= 0xffffffffffff; |
| 191 | 0 | c = Math.floor(unsigned / 0xffffffffff); |
| 192 | 0 | unsigned &= 0xffffffffff; |
| 193 | 0 | d = Math.floor(unsigned / 0xffffffff); |
| 194 | 0 | unsigned &= 0xffffffff; |
| 195 | 0 | e = Math.floor(unsigned / 0xffffff); |
| 196 | 0 | unsigned &= 0xffffff; |
| 197 | 0 | f = Math.floor(unsigned / 0xffff); |
| 198 | 0 | unsigned &= 0xffff; |
| 199 | 0 | g = Math.floor(unsigned / 0xff); |
| 200 | 0 | unsigned &= 0xff; |
| 201 | 0 | h = Math.floor(unsigned); |
| 202 | 0 | return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h); |
| 203 | }; | |
| 204 | ||
| 205 | /** | |
| 206 | * UTF8 methods | |
| 207 | */ | |
| 208 | ||
| 209 | // Take a raw binary string and return a utf8 string | |
| 210 | 1 | BinaryParser.decode_utf8 = function decode_utf8 (binaryStr) { |
| 211 | 0 | var len = binaryStr.length |
| 212 | , decoded = '' | |
| 213 | , i = 0 | |
| 214 | , c = 0 | |
| 215 | , c1 = 0 | |
| 216 | , c2 = 0 | |
| 217 | , c3; | |
| 218 | ||
| 219 | 0 | while (i < len) { |
| 220 | 0 | c = binaryStr.charCodeAt(i); |
| 221 | 0 | if (c < 128) { |
| 222 | 0 | decoded += String.fromCharCode(c); |
| 223 | 0 | i++; |
| 224 | 0 | } else if ((c > 191) && (c < 224)) { |
| 225 | 0 | c2 = binaryStr.charCodeAt(i+1); |
| 226 | 0 | decoded += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); |
| 227 | 0 | i += 2; |
| 228 | } else { | |
| 229 | 0 | c2 = binaryStr.charCodeAt(i+1); |
| 230 | 0 | c3 = binaryStr.charCodeAt(i+2); |
| 231 | 0 | decoded += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); |
| 232 | 0 | i += 3; |
| 233 | } | |
| 234 | } | |
| 235 | ||
| 236 | 0 | return decoded; |
| 237 | }; | |
| 238 | ||
| 239 | // Encode a cstring | |
| 240 | 1 | BinaryParser.encode_cstring = function encode_cstring (s) { |
| 241 | 0 | return unescape(encodeURIComponent(s)) + BinaryParser.fromByte(0); |
| 242 | }; | |
| 243 | ||
| 244 | // Take a utf8 string and return a binary string | |
| 245 | 1 | BinaryParser.encode_utf8 = function encode_utf8 (s) { |
| 246 | 0 | var a = "" |
| 247 | , c; | |
| 248 | ||
| 249 | 0 | for (var n = 0, len = s.length; n < len; n++) { |
| 250 | 0 | c = s.charCodeAt(n); |
| 251 | ||
| 252 | 0 | if (c < 128) { |
| 253 | 0 | a += String.fromCharCode(c); |
| 254 | 0 | } else if ((c > 127) && (c < 2048)) { |
| 255 | 0 | a += String.fromCharCode((c>>6) | 192) ; |
| 256 | 0 | a += String.fromCharCode((c&63) | 128); |
| 257 | } else { | |
| 258 | 0 | a += String.fromCharCode((c>>12) | 224); |
| 259 | 0 | a += String.fromCharCode(((c>>6) & 63) | 128); |
| 260 | 0 | a += String.fromCharCode((c&63) | 128); |
| 261 | } | |
| 262 | } | |
| 263 | ||
| 264 | 0 | return a; |
| 265 | }; | |
| 266 | ||
| 267 | 1 | BinaryParser.hprint = function hprint (s) { |
| 268 | 0 | var number; |
| 269 | ||
| 270 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 271 | 0 | if (s.charCodeAt(i) < 32) { |
| 272 | 0 | number = s.charCodeAt(i) <= 15 |
| 273 | ? "0" + s.charCodeAt(i).toString(16) | |
| 274 | : s.charCodeAt(i).toString(16); | |
| 275 | 0 | process.stdout.write(number + " ") |
| 276 | } else { | |
| 277 | 0 | number = s.charCodeAt(i) <= 15 |
| 278 | ? "0" + s.charCodeAt(i).toString(16) | |
| 279 | : s.charCodeAt(i).toString(16); | |
| 280 | 0 | process.stdout.write(number + " ") |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | 0 | process.stdout.write("\n\n"); |
| 285 | }; | |
| 286 | ||
| 287 | 1 | BinaryParser.ilprint = function hprint (s) { |
| 288 | 0 | var number; |
| 289 | ||
| 290 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 291 | 0 | if (s.charCodeAt(i) < 32) { |
| 292 | 0 | number = s.charCodeAt(i) <= 15 |
| 293 | ? "0" + s.charCodeAt(i).toString(10) | |
| 294 | : s.charCodeAt(i).toString(10); | |
| 295 | ||
| 296 | 0 | require('util').debug(number+' : '); |
| 297 | } else { | |
| 298 | 0 | number = s.charCodeAt(i) <= 15 |
| 299 | ? "0" + s.charCodeAt(i).toString(10) | |
| 300 | : s.charCodeAt(i).toString(10); | |
| 301 | 0 | require('util').debug(number+' : '+ s.charAt(i)); |
| 302 | } | |
| 303 | } | |
| 304 | }; | |
| 305 | ||
| 306 | 1 | BinaryParser.hlprint = function hprint (s) { |
| 307 | 0 | var number; |
| 308 | ||
| 309 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 310 | 0 | if (s.charCodeAt(i) < 32) { |
| 311 | 0 | number = s.charCodeAt(i) <= 15 |
| 312 | ? "0" + s.charCodeAt(i).toString(16) | |
| 313 | : s.charCodeAt(i).toString(16); | |
| 314 | 0 | require('util').debug(number+' : '); |
| 315 | } else { | |
| 316 | 0 | number = s.charCodeAt(i) <= 15 |
| 317 | ? "0" + s.charCodeAt(i).toString(16) | |
| 318 | : s.charCodeAt(i).toString(16); | |
| 319 | 0 | require('util').debug(number+' : '+ s.charAt(i)); |
| 320 | } | |
| 321 | } | |
| 322 | }; | |
| 323 | ||
| 324 | /** | |
| 325 | * BinaryParser buffer constructor. | |
| 326 | */ | |
| 327 | 1 | function BinaryParserBuffer (bigEndian, buffer) { |
| 328 | 0 | this.bigEndian = bigEndian || 0; |
| 329 | 0 | this.buffer = []; |
| 330 | 0 | this.setBuffer(buffer); |
| 331 | }; | |
| 332 | ||
| 333 | 1 | BinaryParserBuffer.prototype.setBuffer = function setBuffer (data) { |
| 334 | 0 | var l, i, b; |
| 335 | ||
| 336 | 0 | if (data) { |
| 337 | 0 | i = l = data.length; |
| 338 | 0 | b = this.buffer = new Array(l); |
| 339 | 0 | for (; i; b[l - i] = data.charCodeAt(--i)); |
| 340 | 0 | this.bigEndian && b.reverse(); |
| 341 | } | |
| 342 | }; | |
| 343 | ||
| 344 | 1 | BinaryParserBuffer.prototype.hasNeededBits = function hasNeededBits (neededBits) { |
| 345 | 0 | return this.buffer.length >= -(-neededBits >> 3); |
| 346 | }; | |
| 347 | ||
| 348 | 1 | BinaryParserBuffer.prototype.checkBuffer = function checkBuffer (neededBits) { |
| 349 | 0 | if (!this.hasNeededBits(neededBits)) { |
| 350 | 0 | throw new Error("checkBuffer::missing bytes"); |
| 351 | } | |
| 352 | }; | |
| 353 | ||
| 354 | 1 | BinaryParserBuffer.prototype.readBits = function readBits (start, length) { |
| 355 | //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) | |
| 356 | ||
| 357 | 0 | function shl (a, b) { |
| 358 | 0 | for (; b--; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); |
| 359 | 0 | return a; |
| 360 | } | |
| 361 | ||
| 362 | 0 | if (start < 0 || length <= 0) { |
| 363 | 0 | return 0; |
| 364 | } | |
| 365 | ||
| 366 | 0 | this.checkBuffer(start + length); |
| 367 | ||
| 368 | 0 | var offsetLeft |
| 369 | , offsetRight = start % 8 | |
| 370 | , curByte = this.buffer.length - ( start >> 3 ) - 1 | |
| 371 | , lastByte = this.buffer.length + ( -( start + length ) >> 3 ) | |
| 372 | , diff = curByte - lastByte | |
| 373 | , sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)) + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[lastByte++] & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight : 0); | |
| 374 | ||
| 375 | 0 | for(; diff; sum += shl(this.buffer[lastByte++], (diff-- << 3) - offsetRight)); |
| 376 | ||
| 377 | 0 | return sum; |
| 378 | }; | |
| 379 | ||
| 380 | /** | |
| 381 | * Expose. | |
| 382 | */ | |
| 383 | 1 | BinaryParser.Buffer = BinaryParserBuffer; |
| 384 | ||
| 385 | 1 | exports.BinaryParser = BinaryParser; |
| 386 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('./long').Long |
| 2 | , Double = require('./double').Double | |
| 3 | , Timestamp = require('./timestamp').Timestamp | |
| 4 | , ObjectID = require('./objectid').ObjectID | |
| 5 | , Symbol = require('./symbol').Symbol | |
| 6 | , Code = require('./code').Code | |
| 7 | , MinKey = require('./min_key').MinKey | |
| 8 | , MaxKey = require('./max_key').MaxKey | |
| 9 | , DBRef = require('./db_ref').DBRef | |
| 10 | , Binary = require('./binary').Binary | |
| 11 | , BinaryParser = require('./binary_parser').BinaryParser | |
| 12 | , writeIEEE754 = require('./float_parser').writeIEEE754 | |
| 13 | , readIEEE754 = require('./float_parser').readIEEE754 | |
| 14 | ||
| 15 | // To ensure that 0.4 of node works correctly | |
| 16 | 1 | var isDate = function isDate(d) { |
| 17 | 0 | return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Create a new BSON instance | |
| 22 | * | |
| 23 | * @class Represents the BSON Parser | |
| 24 | * @return {BSON} instance of BSON Parser. | |
| 25 | */ | |
| 26 | 1 | function BSON () {}; |
| 27 | ||
| 28 | /** | |
| 29 | * @ignore | |
| 30 | * @api private | |
| 31 | */ | |
| 32 | // BSON MAX VALUES | |
| 33 | 1 | BSON.BSON_INT32_MAX = 0x7FFFFFFF; |
| 34 | 1 | BSON.BSON_INT32_MIN = -0x80000000; |
| 35 | ||
| 36 | 1 | BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1; |
| 37 | 1 | BSON.BSON_INT64_MIN = -Math.pow(2, 63); |
| 38 | ||
| 39 | // JS MAX PRECISE VALUES | |
| 40 | 1 | BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double. |
| 41 | 1 | BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double. |
| 42 | ||
| 43 | // Internal long versions | |
| 44 | 1 | var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double. |
| 45 | 1 | var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double. |
| 46 | ||
| 47 | /** | |
| 48 | * Number BSON Type | |
| 49 | * | |
| 50 | * @classconstant BSON_DATA_NUMBER | |
| 51 | **/ | |
| 52 | 1 | BSON.BSON_DATA_NUMBER = 1; |
| 53 | /** | |
| 54 | * String BSON Type | |
| 55 | * | |
| 56 | * @classconstant BSON_DATA_STRING | |
| 57 | **/ | |
| 58 | 1 | BSON.BSON_DATA_STRING = 2; |
| 59 | /** | |
| 60 | * Object BSON Type | |
| 61 | * | |
| 62 | * @classconstant BSON_DATA_OBJECT | |
| 63 | **/ | |
| 64 | 1 | BSON.BSON_DATA_OBJECT = 3; |
| 65 | /** | |
| 66 | * Array BSON Type | |
| 67 | * | |
| 68 | * @classconstant BSON_DATA_ARRAY | |
| 69 | **/ | |
| 70 | 1 | BSON.BSON_DATA_ARRAY = 4; |
| 71 | /** | |
| 72 | * Binary BSON Type | |
| 73 | * | |
| 74 | * @classconstant BSON_DATA_BINARY | |
| 75 | **/ | |
| 76 | 1 | BSON.BSON_DATA_BINARY = 5; |
| 77 | /** | |
| 78 | * ObjectID BSON Type | |
| 79 | * | |
| 80 | * @classconstant BSON_DATA_OID | |
| 81 | **/ | |
| 82 | 1 | BSON.BSON_DATA_OID = 7; |
| 83 | /** | |
| 84 | * Boolean BSON Type | |
| 85 | * | |
| 86 | * @classconstant BSON_DATA_BOOLEAN | |
| 87 | **/ | |
| 88 | 1 | BSON.BSON_DATA_BOOLEAN = 8; |
| 89 | /** | |
| 90 | * Date BSON Type | |
| 91 | * | |
| 92 | * @classconstant BSON_DATA_DATE | |
| 93 | **/ | |
| 94 | 1 | BSON.BSON_DATA_DATE = 9; |
| 95 | /** | |
| 96 | * null BSON Type | |
| 97 | * | |
| 98 | * @classconstant BSON_DATA_NULL | |
| 99 | **/ | |
| 100 | 1 | BSON.BSON_DATA_NULL = 10; |
| 101 | /** | |
| 102 | * RegExp BSON Type | |
| 103 | * | |
| 104 | * @classconstant BSON_DATA_REGEXP | |
| 105 | **/ | |
| 106 | 1 | BSON.BSON_DATA_REGEXP = 11; |
| 107 | /** | |
| 108 | * Code BSON Type | |
| 109 | * | |
| 110 | * @classconstant BSON_DATA_CODE | |
| 111 | **/ | |
| 112 | 1 | BSON.BSON_DATA_CODE = 13; |
| 113 | /** | |
| 114 | * Symbol BSON Type | |
| 115 | * | |
| 116 | * @classconstant BSON_DATA_SYMBOL | |
| 117 | **/ | |
| 118 | 1 | BSON.BSON_DATA_SYMBOL = 14; |
| 119 | /** | |
| 120 | * Code with Scope BSON Type | |
| 121 | * | |
| 122 | * @classconstant BSON_DATA_CODE_W_SCOPE | |
| 123 | **/ | |
| 124 | 1 | BSON.BSON_DATA_CODE_W_SCOPE = 15; |
| 125 | /** | |
| 126 | * 32 bit Integer BSON Type | |
| 127 | * | |
| 128 | * @classconstant BSON_DATA_INT | |
| 129 | **/ | |
| 130 | 1 | BSON.BSON_DATA_INT = 16; |
| 131 | /** | |
| 132 | * Timestamp BSON Type | |
| 133 | * | |
| 134 | * @classconstant BSON_DATA_TIMESTAMP | |
| 135 | **/ | |
| 136 | 1 | BSON.BSON_DATA_TIMESTAMP = 17; |
| 137 | /** | |
| 138 | * Long BSON Type | |
| 139 | * | |
| 140 | * @classconstant BSON_DATA_LONG | |
| 141 | **/ | |
| 142 | 1 | BSON.BSON_DATA_LONG = 18; |
| 143 | /** | |
| 144 | * MinKey BSON Type | |
| 145 | * | |
| 146 | * @classconstant BSON_DATA_MIN_KEY | |
| 147 | **/ | |
| 148 | 1 | BSON.BSON_DATA_MIN_KEY = 0xff; |
| 149 | /** | |
| 150 | * MaxKey BSON Type | |
| 151 | * | |
| 152 | * @classconstant BSON_DATA_MAX_KEY | |
| 153 | **/ | |
| 154 | 1 | BSON.BSON_DATA_MAX_KEY = 0x7f; |
| 155 | ||
| 156 | /** | |
| 157 | * Binary Default Type | |
| 158 | * | |
| 159 | * @classconstant BSON_BINARY_SUBTYPE_DEFAULT | |
| 160 | **/ | |
| 161 | 1 | BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; |
| 162 | /** | |
| 163 | * Binary Function Type | |
| 164 | * | |
| 165 | * @classconstant BSON_BINARY_SUBTYPE_FUNCTION | |
| 166 | **/ | |
| 167 | 1 | BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; |
| 168 | /** | |
| 169 | * Binary Byte Array Type | |
| 170 | * | |
| 171 | * @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY | |
| 172 | **/ | |
| 173 | 1 | BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; |
| 174 | /** | |
| 175 | * Binary UUID Type | |
| 176 | * | |
| 177 | * @classconstant BSON_BINARY_SUBTYPE_UUID | |
| 178 | **/ | |
| 179 | 1 | BSON.BSON_BINARY_SUBTYPE_UUID = 3; |
| 180 | /** | |
| 181 | * Binary MD5 Type | |
| 182 | * | |
| 183 | * @classconstant BSON_BINARY_SUBTYPE_MD5 | |
| 184 | **/ | |
| 185 | 1 | BSON.BSON_BINARY_SUBTYPE_MD5 = 4; |
| 186 | /** | |
| 187 | * Binary User Defined Type | |
| 188 | * | |
| 189 | * @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED | |
| 190 | **/ | |
| 191 | 1 | BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; |
| 192 | ||
| 193 | /** | |
| 194 | * Calculate the bson size for a passed in Javascript object. | |
| 195 | * | |
| 196 | * @param {Object} object the Javascript object to calculate the BSON byte size for. | |
| 197 | * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. | |
| 198 | * @return {Number} returns the number of bytes the BSON object will take up. | |
| 199 | * @api public | |
| 200 | */ | |
| 201 | 1 | BSON.calculateObjectSize = function calculateObjectSize(object, serializeFunctions) { |
| 202 | 0 | var totalLength = (4 + 1); |
| 203 | ||
| 204 | 0 | if(Array.isArray(object)) { |
| 205 | 0 | for(var i = 0; i < object.length; i++) { |
| 206 | 0 | totalLength += calculateElement(i.toString(), object[i], serializeFunctions) |
| 207 | } | |
| 208 | } else { | |
| 209 | // If we have toBSON defined, override the current object | |
| 210 | 0 | if(object.toBSON) { |
| 211 | 0 | object = object.toBSON(); |
| 212 | } | |
| 213 | ||
| 214 | // Calculate size | |
| 215 | 0 | for(var key in object) { |
| 216 | 0 | totalLength += calculateElement(key, object[key], serializeFunctions) |
| 217 | } | |
| 218 | } | |
| 219 | ||
| 220 | 0 | return totalLength; |
| 221 | } | |
| 222 | ||
| 223 | /** | |
| 224 | * @ignore | |
| 225 | * @api private | |
| 226 | */ | |
| 227 | 1 | function calculateElement(name, value, serializeFunctions) { |
| 228 | 0 | var isBuffer = typeof Buffer !== 'undefined'; |
| 229 | ||
| 230 | 0 | switch(typeof value) { |
| 231 | case 'string': | |
| 232 | 0 | return 1 + (!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1 + 4 + (!isBuffer ? numberOfBytes(value) : Buffer.byteLength(value, 'utf8')) + 1; |
| 233 | case 'number': | |
| 234 | 0 | if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 235 | 0 | if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { // 32 bit |
| 236 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (4 + 1); |
| 237 | } else { | |
| 238 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 239 | } | |
| 240 | } else { // 64 bit | |
| 241 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 242 | } | |
| 243 | case 'undefined': | |
| 244 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); |
| 245 | case 'boolean': | |
| 246 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 1); |
| 247 | case 'object': | |
| 248 | 0 | if(value == null || value instanceof MinKey || value instanceof MaxKey || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { |
| 249 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); |
| 250 | 0 | } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { |
| 251 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (12 + 1); |
| 252 | 0 | } else if(value instanceof Date || isDate(value)) { |
| 253 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 254 | 0 | } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { |
| 255 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 4 + 1) + value.length; |
| 256 | 0 | } else if(value instanceof Long || value instanceof Double || value instanceof Timestamp |
| 257 | || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Double' || value['_bsontype'] == 'Timestamp') { | |
| 258 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 259 | 0 | } else if(value instanceof Code || value['_bsontype'] == 'Code') { |
| 260 | // Calculate size depending on the availability of a scope | |
| 261 | 0 | if(value.scope != null && Object.keys(value.scope).length > 0) { |
| 262 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 263 | } else { | |
| 264 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1; |
| 265 | } | |
| 266 | 0 | } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { |
| 267 | // Check what kind of subtype we have | |
| 268 | 0 | if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) { |
| 269 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1 + 4); |
| 270 | } else { | |
| 271 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1); |
| 272 | } | |
| 273 | 0 | } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { |
| 274 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + ((!isBuffer ? numberOfBytes(value.value) : Buffer.byteLength(value.value, 'utf8')) + 4 + 1 + 1); |
| 275 | 0 | } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { |
| 276 | // Set up correct object for serialization | |
| 277 | 0 | var ordered_values = { |
| 278 | '$ref': value.namespace | |
| 279 | , '$id' : value.oid | |
| 280 | }; | |
| 281 | ||
| 282 | // Add db reference if it exists | |
| 283 | 0 | if(null != value.db) { |
| 284 | 0 | ordered_values['$db'] = value.db; |
| 285 | } | |
| 286 | ||
| 287 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + BSON.calculateObjectSize(ordered_values, serializeFunctions); |
| 288 | 0 | } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { |
| 289 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 |
| 290 | + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 | |
| 291 | } else { | |
| 292 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + BSON.calculateObjectSize(value, serializeFunctions) + 1; |
| 293 | } | |
| 294 | case 'function': | |
| 295 | // WTF for 0.4.X where typeof /someregexp/ === 'function' | |
| 296 | 0 | if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { |
| 297 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 |
| 298 | + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 | |
| 299 | } else { | |
| 300 | 0 | if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { |
| 301 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 302 | 0 | } else if(serializeFunctions) { |
| 303 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1; |
| 304 | } | |
| 305 | } | |
| 306 | } | |
| 307 | ||
| 308 | 0 | return 0; |
| 309 | } | |
| 310 | ||
| 311 | /** | |
| 312 | * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. | |
| 313 | * | |
| 314 | * @param {Object} object the Javascript object to serialize. | |
| 315 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 316 | * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. | |
| 317 | * @param {Number} index the index in the buffer where we wish to start serializing into. | |
| 318 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 319 | * @return {Number} returns the new write index in the Buffer. | |
| 320 | * @api public | |
| 321 | */ | |
| 322 | 1 | BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index, serializeFunctions) { |
| 323 | // Default setting false | |
| 324 | 0 | serializeFunctions = serializeFunctions == null ? false : serializeFunctions; |
| 325 | // Write end information (length of the object) | |
| 326 | 0 | var size = buffer.length; |
| 327 | // Write the size of the object | |
| 328 | 0 | buffer[index++] = size & 0xff; |
| 329 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 330 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 331 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 332 | 0 | return serializeObject(object, checkKeys, buffer, index, serializeFunctions) - 1; |
| 333 | } | |
| 334 | ||
| 335 | /** | |
| 336 | * @ignore | |
| 337 | * @api private | |
| 338 | */ | |
| 339 | 1 | var serializeObject = function(object, checkKeys, buffer, index, serializeFunctions) { |
| 340 | // Process the object | |
| 341 | 0 | if(Array.isArray(object)) { |
| 342 | 0 | for(var i = 0; i < object.length; i++) { |
| 343 | 0 | index = packElement(i.toString(), object[i], checkKeys, buffer, index, serializeFunctions); |
| 344 | } | |
| 345 | } else { | |
| 346 | // If we have toBSON defined, override the current object | |
| 347 | 0 | if(object.toBSON) { |
| 348 | 0 | object = object.toBSON(); |
| 349 | } | |
| 350 | ||
| 351 | // Serialize the object | |
| 352 | 0 | for(var key in object) { |
| 353 | // Check the key and throw error if it's illegal | |
| 354 | 0 | if (key != '$db' && key != '$ref' && key != '$id') { |
| 355 | // dollars and dots ok | |
| 356 | 0 | BSON.checkKey(key, !checkKeys); |
| 357 | } | |
| 358 | ||
| 359 | // Pack the element | |
| 360 | 0 | index = packElement(key, object[key], checkKeys, buffer, index, serializeFunctions); |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | // Write zero | |
| 365 | 0 | buffer[index++] = 0; |
| 366 | 0 | return index; |
| 367 | } | |
| 368 | ||
| 369 | 1 | var stringToBytes = function(str) { |
| 370 | 0 | var ch, st, re = []; |
| 371 | 0 | for (var i = 0; i < str.length; i++ ) { |
| 372 | 0 | ch = str.charCodeAt(i); // get char |
| 373 | 0 | st = []; // set up "stack" |
| 374 | 0 | do { |
| 375 | 0 | st.push( ch & 0xFF ); // push byte to stack |
| 376 | 0 | ch = ch >> 8; // shift value down by 1 byte |
| 377 | } | |
| 378 | while ( ch ); | |
| 379 | // add stack contents to result | |
| 380 | // done because chars have "wrong" endianness | |
| 381 | 0 | re = re.concat( st.reverse() ); |
| 382 | } | |
| 383 | // return an array of bytes | |
| 384 | 0 | return re; |
| 385 | } | |
| 386 | ||
| 387 | 1 | var numberOfBytes = function(str) { |
| 388 | 0 | var ch, st, re = 0; |
| 389 | 0 | for (var i = 0; i < str.length; i++ ) { |
| 390 | 0 | ch = str.charCodeAt(i); // get char |
| 391 | 0 | st = []; // set up "stack" |
| 392 | 0 | do { |
| 393 | 0 | st.push( ch & 0xFF ); // push byte to stack |
| 394 | 0 | ch = ch >> 8; // shift value down by 1 byte |
| 395 | } | |
| 396 | while ( ch ); | |
| 397 | // add stack contents to result | |
| 398 | // done because chars have "wrong" endianness | |
| 399 | 0 | re = re + st.length; |
| 400 | } | |
| 401 | // return an array of bytes | |
| 402 | 0 | return re; |
| 403 | } | |
| 404 | ||
| 405 | /** | |
| 406 | * @ignore | |
| 407 | * @api private | |
| 408 | */ | |
| 409 | 1 | var writeToTypedArray = function(buffer, string, index) { |
| 410 | 0 | var bytes = stringToBytes(string); |
| 411 | 0 | for(var i = 0; i < bytes.length; i++) { |
| 412 | 0 | buffer[index + i] = bytes[i]; |
| 413 | } | |
| 414 | 0 | return bytes.length; |
| 415 | } | |
| 416 | ||
| 417 | /** | |
| 418 | * @ignore | |
| 419 | * @api private | |
| 420 | */ | |
| 421 | 1 | var supportsBuffer = typeof Buffer != 'undefined'; |
| 422 | ||
| 423 | /** | |
| 424 | * @ignore | |
| 425 | * @api private | |
| 426 | */ | |
| 427 | 1 | var packElement = function(name, value, checkKeys, buffer, index, serializeFunctions) { |
| 428 | 0 | var startIndex = index; |
| 429 | ||
| 430 | 0 | switch(typeof value) { |
| 431 | case 'string': | |
| 432 | // Encode String type | |
| 433 | 0 | buffer[index++] = BSON.BSON_DATA_STRING; |
| 434 | // Number of written bytes | |
| 435 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 436 | // Encode the name | |
| 437 | 0 | index = index + numberOfWrittenBytes + 1; |
| 438 | 0 | buffer[index - 1] = 0; |
| 439 | ||
| 440 | // Calculate size | |
| 441 | 0 | var size = supportsBuffer ? Buffer.byteLength(value) + 1 : numberOfBytes(value) + 1; |
| 442 | // Write the size of the string to buffer | |
| 443 | 0 | buffer[index + 3] = (size >> 24) & 0xff; |
| 444 | 0 | buffer[index + 2] = (size >> 16) & 0xff; |
| 445 | 0 | buffer[index + 1] = (size >> 8) & 0xff; |
| 446 | 0 | buffer[index] = size & 0xff; |
| 447 | // Ajust the index | |
| 448 | 0 | index = index + 4; |
| 449 | // Write the string | |
| 450 | 0 | supportsBuffer ? buffer.write(value, index, 'utf8') : writeToTypedArray(buffer, value, index); |
| 451 | // Update index | |
| 452 | 0 | index = index + size - 1; |
| 453 | // Write zero | |
| 454 | 0 | buffer[index++] = 0; |
| 455 | // Return index | |
| 456 | 0 | return index; |
| 457 | case 'number': | |
| 458 | // We have an integer value | |
| 459 | 0 | if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 460 | // If the value fits in 32 bits encode as int, if it fits in a double | |
| 461 | // encode it as a double, otherwise long | |
| 462 | 0 | if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { |
| 463 | // Set int type 32 bits or less | |
| 464 | 0 | buffer[index++] = BSON.BSON_DATA_INT; |
| 465 | // Number of written bytes | |
| 466 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 467 | // Encode the name | |
| 468 | 0 | index = index + numberOfWrittenBytes + 1; |
| 469 | 0 | buffer[index - 1] = 0; |
| 470 | // Write the int value | |
| 471 | 0 | buffer[index++] = value & 0xff; |
| 472 | 0 | buffer[index++] = (value >> 8) & 0xff; |
| 473 | 0 | buffer[index++] = (value >> 16) & 0xff; |
| 474 | 0 | buffer[index++] = (value >> 24) & 0xff; |
| 475 | 0 | } else if(value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 476 | // Encode as double | |
| 477 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 478 | // Number of written bytes | |
| 479 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 480 | // Encode the name | |
| 481 | 0 | index = index + numberOfWrittenBytes + 1; |
| 482 | 0 | buffer[index - 1] = 0; |
| 483 | // Write float | |
| 484 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 485 | // Ajust index | |
| 486 | 0 | index = index + 8; |
| 487 | } else { | |
| 488 | // Set long type | |
| 489 | 0 | buffer[index++] = BSON.BSON_DATA_LONG; |
| 490 | // Number of written bytes | |
| 491 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 492 | // Encode the name | |
| 493 | 0 | index = index + numberOfWrittenBytes + 1; |
| 494 | 0 | buffer[index - 1] = 0; |
| 495 | 0 | var longVal = Long.fromNumber(value); |
| 496 | 0 | var lowBits = longVal.getLowBits(); |
| 497 | 0 | var highBits = longVal.getHighBits(); |
| 498 | // Encode low bits | |
| 499 | 0 | buffer[index++] = lowBits & 0xff; |
| 500 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 501 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 502 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 503 | // Encode high bits | |
| 504 | 0 | buffer[index++] = highBits & 0xff; |
| 505 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 506 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 507 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 508 | } | |
| 509 | } else { | |
| 510 | // Encode as double | |
| 511 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 512 | // Number of written bytes | |
| 513 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 514 | // Encode the name | |
| 515 | 0 | index = index + numberOfWrittenBytes + 1; |
| 516 | 0 | buffer[index - 1] = 0; |
| 517 | // Write float | |
| 518 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 519 | // Ajust index | |
| 520 | 0 | index = index + 8; |
| 521 | } | |
| 522 | ||
| 523 | 0 | return index; |
| 524 | case 'undefined': | |
| 525 | // Set long type | |
| 526 | 0 | buffer[index++] = BSON.BSON_DATA_NULL; |
| 527 | // Number of written bytes | |
| 528 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 529 | // Encode the name | |
| 530 | 0 | index = index + numberOfWrittenBytes + 1; |
| 531 | 0 | buffer[index - 1] = 0; |
| 532 | 0 | return index; |
| 533 | case 'boolean': | |
| 534 | // Write the type | |
| 535 | 0 | buffer[index++] = BSON.BSON_DATA_BOOLEAN; |
| 536 | // Number of written bytes | |
| 537 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 538 | // Encode the name | |
| 539 | 0 | index = index + numberOfWrittenBytes + 1; |
| 540 | 0 | buffer[index - 1] = 0; |
| 541 | // Encode the boolean value | |
| 542 | 0 | buffer[index++] = value ? 1 : 0; |
| 543 | 0 | return index; |
| 544 | case 'object': | |
| 545 | 0 | if(value === null || value instanceof MinKey || value instanceof MaxKey |
| 546 | || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { | |
| 547 | // Write the type of either min or max key | |
| 548 | 0 | if(value === null) { |
| 549 | 0 | buffer[index++] = BSON.BSON_DATA_NULL; |
| 550 | 0 | } else if(value instanceof MinKey) { |
| 551 | 0 | buffer[index++] = BSON.BSON_DATA_MIN_KEY; |
| 552 | } else { | |
| 553 | 0 | buffer[index++] = BSON.BSON_DATA_MAX_KEY; |
| 554 | } | |
| 555 | ||
| 556 | // Number of written bytes | |
| 557 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 558 | // Encode the name | |
| 559 | 0 | index = index + numberOfWrittenBytes + 1; |
| 560 | 0 | buffer[index - 1] = 0; |
| 561 | 0 | return index; |
| 562 | 0 | } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { |
| 563 | // Write the type | |
| 564 | 0 | buffer[index++] = BSON.BSON_DATA_OID; |
| 565 | // Number of written bytes | |
| 566 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 567 | // Encode the name | |
| 568 | 0 | index = index + numberOfWrittenBytes + 1; |
| 569 | 0 | buffer[index - 1] = 0; |
| 570 | ||
| 571 | // Write objectid | |
| 572 | 0 | supportsBuffer ? buffer.write(value.id, index, 'binary') : writeToTypedArray(buffer, value.id, index); |
| 573 | // Ajust index | |
| 574 | 0 | index = index + 12; |
| 575 | 0 | return index; |
| 576 | 0 | } else if(value instanceof Date || isDate(value)) { |
| 577 | // Write the type | |
| 578 | 0 | buffer[index++] = BSON.BSON_DATA_DATE; |
| 579 | // Number of written bytes | |
| 580 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 581 | // Encode the name | |
| 582 | 0 | index = index + numberOfWrittenBytes + 1; |
| 583 | 0 | buffer[index - 1] = 0; |
| 584 | ||
| 585 | // Write the date | |
| 586 | 0 | var dateInMilis = Long.fromNumber(value.getTime()); |
| 587 | 0 | var lowBits = dateInMilis.getLowBits(); |
| 588 | 0 | var highBits = dateInMilis.getHighBits(); |
| 589 | // Encode low bits | |
| 590 | 0 | buffer[index++] = lowBits & 0xff; |
| 591 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 592 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 593 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 594 | // Encode high bits | |
| 595 | 0 | buffer[index++] = highBits & 0xff; |
| 596 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 597 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 598 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 599 | 0 | return index; |
| 600 | 0 | } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { |
| 601 | // Write the type | |
| 602 | 0 | buffer[index++] = BSON.BSON_DATA_BINARY; |
| 603 | // Number of written bytes | |
| 604 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 605 | // Encode the name | |
| 606 | 0 | index = index + numberOfWrittenBytes + 1; |
| 607 | 0 | buffer[index - 1] = 0; |
| 608 | // Get size of the buffer (current write point) | |
| 609 | 0 | var size = value.length; |
| 610 | // Write the size of the string to buffer | |
| 611 | 0 | buffer[index++] = size & 0xff; |
| 612 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 613 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 614 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 615 | // Write the default subtype | |
| 616 | 0 | buffer[index++] = BSON.BSON_BINARY_SUBTYPE_DEFAULT; |
| 617 | // Copy the content form the binary field to the buffer | |
| 618 | 0 | value.copy(buffer, index, 0, size); |
| 619 | // Adjust the index | |
| 620 | 0 | index = index + size; |
| 621 | 0 | return index; |
| 622 | 0 | } else if(value instanceof Long || value instanceof Timestamp || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Timestamp') { |
| 623 | // Write the type | |
| 624 | 0 | buffer[index++] = value instanceof Long ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_TIMESTAMP; |
| 625 | // Number of written bytes | |
| 626 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 627 | // Encode the name | |
| 628 | 0 | index = index + numberOfWrittenBytes + 1; |
| 629 | 0 | buffer[index - 1] = 0; |
| 630 | // Write the date | |
| 631 | 0 | var lowBits = value.getLowBits(); |
| 632 | 0 | var highBits = value.getHighBits(); |
| 633 | // Encode low bits | |
| 634 | 0 | buffer[index++] = lowBits & 0xff; |
| 635 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 636 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 637 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 638 | // Encode high bits | |
| 639 | 0 | buffer[index++] = highBits & 0xff; |
| 640 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 641 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 642 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 643 | 0 | return index; |
| 644 | 0 | } else if(value instanceof Double || value['_bsontype'] == 'Double') { |
| 645 | // Encode as double | |
| 646 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 647 | // Number of written bytes | |
| 648 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 649 | // Encode the name | |
| 650 | 0 | index = index + numberOfWrittenBytes + 1; |
| 651 | 0 | buffer[index - 1] = 0; |
| 652 | // Write float | |
| 653 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 654 | // Ajust index | |
| 655 | 0 | index = index + 8; |
| 656 | 0 | return index; |
| 657 | 0 | } else if(value instanceof Code || value['_bsontype'] == 'Code') { |
| 658 | 0 | if(value.scope != null && Object.keys(value.scope).length > 0) { |
| 659 | // Write the type | |
| 660 | 0 | buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; |
| 661 | // Number of written bytes | |
| 662 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 663 | // Encode the name | |
| 664 | 0 | index = index + numberOfWrittenBytes + 1; |
| 665 | 0 | buffer[index - 1] = 0; |
| 666 | // Calculate the scope size | |
| 667 | 0 | var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 668 | // Function string | |
| 669 | 0 | var functionString = value.code.toString(); |
| 670 | // Function Size | |
| 671 | 0 | var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 672 | ||
| 673 | // Calculate full size of the object | |
| 674 | 0 | var totalSize = 4 + codeSize + scopeSize + 4; |
| 675 | ||
| 676 | // Write the total size of the object | |
| 677 | 0 | buffer[index++] = totalSize & 0xff; |
| 678 | 0 | buffer[index++] = (totalSize >> 8) & 0xff; |
| 679 | 0 | buffer[index++] = (totalSize >> 16) & 0xff; |
| 680 | 0 | buffer[index++] = (totalSize >> 24) & 0xff; |
| 681 | ||
| 682 | // Write the size of the string to buffer | |
| 683 | 0 | buffer[index++] = codeSize & 0xff; |
| 684 | 0 | buffer[index++] = (codeSize >> 8) & 0xff; |
| 685 | 0 | buffer[index++] = (codeSize >> 16) & 0xff; |
| 686 | 0 | buffer[index++] = (codeSize >> 24) & 0xff; |
| 687 | ||
| 688 | // Write the string | |
| 689 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 690 | // Update index | |
| 691 | 0 | index = index + codeSize - 1; |
| 692 | // Write zero | |
| 693 | 0 | buffer[index++] = 0; |
| 694 | // Serialize the scope object | |
| 695 | 0 | var scopeObjectBuffer = supportsBuffer ? new Buffer(scopeSize) : new Uint8Array(new ArrayBuffer(scopeSize)); |
| 696 | // Execute the serialization into a seperate buffer | |
| 697 | 0 | serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); |
| 698 | ||
| 699 | // Adjusted scope Size (removing the header) | |
| 700 | 0 | var scopeDocSize = scopeSize; |
| 701 | // Write scope object size | |
| 702 | 0 | buffer[index++] = scopeDocSize & 0xff; |
| 703 | 0 | buffer[index++] = (scopeDocSize >> 8) & 0xff; |
| 704 | 0 | buffer[index++] = (scopeDocSize >> 16) & 0xff; |
| 705 | 0 | buffer[index++] = (scopeDocSize >> 24) & 0xff; |
| 706 | ||
| 707 | // Write the scopeObject into the buffer | |
| 708 | 0 | supportsBuffer ? scopeObjectBuffer.copy(buffer, index, 0, scopeSize) : buffer.set(scopeObjectBuffer, index); |
| 709 | // Adjust index, removing the empty size of the doc (5 bytes 0000000005) | |
| 710 | 0 | index = index + scopeDocSize - 5; |
| 711 | // Write trailing zero | |
| 712 | 0 | buffer[index++] = 0; |
| 713 | 0 | return index |
| 714 | } else { | |
| 715 | 0 | buffer[index++] = BSON.BSON_DATA_CODE; |
| 716 | // Number of written bytes | |
| 717 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 718 | // Encode the name | |
| 719 | 0 | index = index + numberOfWrittenBytes + 1; |
| 720 | 0 | buffer[index - 1] = 0; |
| 721 | // Function string | |
| 722 | 0 | var functionString = value.code.toString(); |
| 723 | // Function Size | |
| 724 | 0 | var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 725 | // Write the size of the string to buffer | |
| 726 | 0 | buffer[index++] = size & 0xff; |
| 727 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 728 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 729 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 730 | // Write the string | |
| 731 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 732 | // Update index | |
| 733 | 0 | index = index + size - 1; |
| 734 | // Write zero | |
| 735 | 0 | buffer[index++] = 0; |
| 736 | 0 | return index; |
| 737 | } | |
| 738 | 0 | } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { |
| 739 | // Write the type | |
| 740 | 0 | buffer[index++] = BSON.BSON_DATA_BINARY; |
| 741 | // Number of written bytes | |
| 742 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 743 | // Encode the name | |
| 744 | 0 | index = index + numberOfWrittenBytes + 1; |
| 745 | 0 | buffer[index - 1] = 0; |
| 746 | // Extract the buffer | |
| 747 | 0 | var data = value.value(true); |
| 748 | // Calculate size | |
| 749 | 0 | var size = value.position; |
| 750 | // Write the size of the string to buffer | |
| 751 | 0 | buffer[index++] = size & 0xff; |
| 752 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 753 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 754 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 755 | // Write the subtype to the buffer | |
| 756 | 0 | buffer[index++] = value.sub_type; |
| 757 | ||
| 758 | // If we have binary type 2 the 4 first bytes are the size | |
| 759 | 0 | if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) { |
| 760 | 0 | buffer[index++] = size & 0xff; |
| 761 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 762 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 763 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 764 | } | |
| 765 | ||
| 766 | // Write the data to the object | |
| 767 | 0 | supportsBuffer ? data.copy(buffer, index, 0, value.position) : buffer.set(data, index); |
| 768 | // Ajust index | |
| 769 | 0 | index = index + value.position; |
| 770 | 0 | return index; |
| 771 | 0 | } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { |
| 772 | // Write the type | |
| 773 | 0 | buffer[index++] = BSON.BSON_DATA_SYMBOL; |
| 774 | // Number of written bytes | |
| 775 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 776 | // Encode the name | |
| 777 | 0 | index = index + numberOfWrittenBytes + 1; |
| 778 | 0 | buffer[index - 1] = 0; |
| 779 | // Calculate size | |
| 780 | 0 | var size = supportsBuffer ? Buffer.byteLength(value.value) + 1 : numberOfBytes(value.value) + 1; |
| 781 | // Write the size of the string to buffer | |
| 782 | 0 | buffer[index++] = size & 0xff; |
| 783 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 784 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 785 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 786 | // Write the string | |
| 787 | 0 | buffer.write(value.value, index, 'utf8'); |
| 788 | // Update index | |
| 789 | 0 | index = index + size - 1; |
| 790 | // Write zero | |
| 791 | 0 | buffer[index++] = 0x00; |
| 792 | 0 | return index; |
| 793 | 0 | } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { |
| 794 | // Write the type | |
| 795 | 0 | buffer[index++] = BSON.BSON_DATA_OBJECT; |
| 796 | // Number of written bytes | |
| 797 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 798 | // Encode the name | |
| 799 | 0 | index = index + numberOfWrittenBytes + 1; |
| 800 | 0 | buffer[index - 1] = 0; |
| 801 | // Set up correct object for serialization | |
| 802 | 0 | var ordered_values = { |
| 803 | '$ref': value.namespace | |
| 804 | , '$id' : value.oid | |
| 805 | }; | |
| 806 | ||
| 807 | // Add db reference if it exists | |
| 808 | 0 | if(null != value.db) { |
| 809 | 0 | ordered_values['$db'] = value.db; |
| 810 | } | |
| 811 | ||
| 812 | // Message size | |
| 813 | 0 | var size = BSON.calculateObjectSize(ordered_values, serializeFunctions); |
| 814 | // Serialize the object | |
| 815 | 0 | var endIndex = BSON.serializeWithBufferAndIndex(ordered_values, checkKeys, buffer, index, serializeFunctions); |
| 816 | // Write the size of the string to buffer | |
| 817 | 0 | buffer[index++] = size & 0xff; |
| 818 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 819 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 820 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 821 | // Write zero for object | |
| 822 | 0 | buffer[endIndex++] = 0x00; |
| 823 | // Return the end index | |
| 824 | 0 | return endIndex; |
| 825 | 0 | } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { |
| 826 | // Write the type | |
| 827 | 0 | buffer[index++] = BSON.BSON_DATA_REGEXP; |
| 828 | // Number of written bytes | |
| 829 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 830 | // Encode the name | |
| 831 | 0 | index = index + numberOfWrittenBytes + 1; |
| 832 | 0 | buffer[index - 1] = 0; |
| 833 | ||
| 834 | // Write the regular expression string | |
| 835 | 0 | supportsBuffer ? buffer.write(value.source, index, 'utf8') : writeToTypedArray(buffer, value.source, index); |
| 836 | // Adjust the index | |
| 837 | 0 | index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); |
| 838 | // Write zero | |
| 839 | 0 | buffer[index++] = 0x00; |
| 840 | // Write the parameters | |
| 841 | 0 | if(value.global) buffer[index++] = 0x73; // s |
| 842 | 0 | if(value.ignoreCase) buffer[index++] = 0x69; // i |
| 843 | 0 | if(value.multiline) buffer[index++] = 0x6d; // m |
| 844 | // Add ending zero | |
| 845 | 0 | buffer[index++] = 0x00; |
| 846 | 0 | return index; |
| 847 | } else { | |
| 848 | // Write the type | |
| 849 | 0 | buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT; |
| 850 | // Number of written bytes | |
| 851 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 852 | // Adjust the index | |
| 853 | 0 | index = index + numberOfWrittenBytes + 1; |
| 854 | 0 | buffer[index - 1] = 0; |
| 855 | 0 | var endIndex = serializeObject(value, checkKeys, buffer, index + 4, serializeFunctions); |
| 856 | // Write size | |
| 857 | 0 | var size = endIndex - index; |
| 858 | // Write the size of the string to buffer | |
| 859 | 0 | buffer[index++] = size & 0xff; |
| 860 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 861 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 862 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 863 | 0 | return endIndex; |
| 864 | } | |
| 865 | case 'function': | |
| 866 | // WTF for 0.4.X where typeof /someregexp/ === 'function' | |
| 867 | 0 | if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { |
| 868 | // Write the type | |
| 869 | 0 | buffer[index++] = BSON.BSON_DATA_REGEXP; |
| 870 | // Number of written bytes | |
| 871 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 872 | // Encode the name | |
| 873 | 0 | index = index + numberOfWrittenBytes + 1; |
| 874 | 0 | buffer[index - 1] = 0; |
| 875 | ||
| 876 | // Write the regular expression string | |
| 877 | 0 | buffer.write(value.source, index, 'utf8'); |
| 878 | // Adjust the index | |
| 879 | 0 | index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); |
| 880 | // Write zero | |
| 881 | 0 | buffer[index++] = 0x00; |
| 882 | // Write the parameters | |
| 883 | 0 | if(value.global) buffer[index++] = 0x73; // s |
| 884 | 0 | if(value.ignoreCase) buffer[index++] = 0x69; // i |
| 885 | 0 | if(value.multiline) buffer[index++] = 0x6d; // m |
| 886 | // Add ending zero | |
| 887 | 0 | buffer[index++] = 0x00; |
| 888 | 0 | return index; |
| 889 | } else { | |
| 890 | 0 | if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { |
| 891 | // Write the type | |
| 892 | 0 | buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; |
| 893 | // Number of written bytes | |
| 894 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 895 | // Encode the name | |
| 896 | 0 | index = index + numberOfWrittenBytes + 1; |
| 897 | 0 | buffer[index - 1] = 0; |
| 898 | // Calculate the scope size | |
| 899 | 0 | var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 900 | // Function string | |
| 901 | 0 | var functionString = value.toString(); |
| 902 | // Function Size | |
| 903 | 0 | var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 904 | ||
| 905 | // Calculate full size of the object | |
| 906 | 0 | var totalSize = 4 + codeSize + scopeSize; |
| 907 | ||
| 908 | // Write the total size of the object | |
| 909 | 0 | buffer[index++] = totalSize & 0xff; |
| 910 | 0 | buffer[index++] = (totalSize >> 8) & 0xff; |
| 911 | 0 | buffer[index++] = (totalSize >> 16) & 0xff; |
| 912 | 0 | buffer[index++] = (totalSize >> 24) & 0xff; |
| 913 | ||
| 914 | // Write the size of the string to buffer | |
| 915 | 0 | buffer[index++] = codeSize & 0xff; |
| 916 | 0 | buffer[index++] = (codeSize >> 8) & 0xff; |
| 917 | 0 | buffer[index++] = (codeSize >> 16) & 0xff; |
| 918 | 0 | buffer[index++] = (codeSize >> 24) & 0xff; |
| 919 | ||
| 920 | // Write the string | |
| 921 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 922 | // Update index | |
| 923 | 0 | index = index + codeSize - 1; |
| 924 | // Write zero | |
| 925 | 0 | buffer[index++] = 0; |
| 926 | // Serialize the scope object | |
| 927 | 0 | var scopeObjectBuffer = new Buffer(scopeSize); |
| 928 | // Execute the serialization into a seperate buffer | |
| 929 | 0 | serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); |
| 930 | ||
| 931 | // Adjusted scope Size (removing the header) | |
| 932 | 0 | var scopeDocSize = scopeSize - 4; |
| 933 | // Write scope object size | |
| 934 | 0 | buffer[index++] = scopeDocSize & 0xff; |
| 935 | 0 | buffer[index++] = (scopeDocSize >> 8) & 0xff; |
| 936 | 0 | buffer[index++] = (scopeDocSize >> 16) & 0xff; |
| 937 | 0 | buffer[index++] = (scopeDocSize >> 24) & 0xff; |
| 938 | ||
| 939 | // Write the scopeObject into the buffer | |
| 940 | 0 | scopeObjectBuffer.copy(buffer, index, 0, scopeSize); |
| 941 | ||
| 942 | // Adjust index, removing the empty size of the doc (5 bytes 0000000005) | |
| 943 | 0 | index = index + scopeDocSize - 5; |
| 944 | // Write trailing zero | |
| 945 | 0 | buffer[index++] = 0; |
| 946 | 0 | return index |
| 947 | 0 | } else if(serializeFunctions) { |
| 948 | 0 | buffer[index++] = BSON.BSON_DATA_CODE; |
| 949 | // Number of written bytes | |
| 950 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 951 | // Encode the name | |
| 952 | 0 | index = index + numberOfWrittenBytes + 1; |
| 953 | 0 | buffer[index - 1] = 0; |
| 954 | // Function string | |
| 955 | 0 | var functionString = value.toString(); |
| 956 | // Function Size | |
| 957 | 0 | var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 958 | // Write the size of the string to buffer | |
| 959 | 0 | buffer[index++] = size & 0xff; |
| 960 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 961 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 962 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 963 | // Write the string | |
| 964 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 965 | // Update index | |
| 966 | 0 | index = index + size - 1; |
| 967 | // Write zero | |
| 968 | 0 | buffer[index++] = 0; |
| 969 | 0 | return index; |
| 970 | } | |
| 971 | } | |
| 972 | } | |
| 973 | ||
| 974 | // If no value to serialize | |
| 975 | 0 | return index; |
| 976 | } | |
| 977 | ||
| 978 | /** | |
| 979 | * Serialize a Javascript object. | |
| 980 | * | |
| 981 | * @param {Object} object the Javascript object to serialize. | |
| 982 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 983 | * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. | |
| 984 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 985 | * @return {Buffer} returns the Buffer object containing the serialized object. | |
| 986 | * @api public | |
| 987 | */ | |
| 988 | 1 | BSON.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { |
| 989 | // Throw error if we are trying serialize an illegal type | |
| 990 | 0 | if(object == null || typeof object != 'object' || Array.isArray(object)) |
| 991 | 0 | throw new Error("Only javascript objects supported"); |
| 992 | ||
| 993 | // Emoty target buffer | |
| 994 | 0 | var buffer = null; |
| 995 | // Calculate the size of the object | |
| 996 | 0 | var size = BSON.calculateObjectSize(object, serializeFunctions); |
| 997 | // Fetch the best available type for storing the binary data | |
| 998 | 0 | if(buffer = typeof Buffer != 'undefined') { |
| 999 | 0 | buffer = new Buffer(size); |
| 1000 | 0 | asBuffer = true; |
| 1001 | 0 | } else if(typeof Uint8Array != 'undefined') { |
| 1002 | 0 | buffer = new Uint8Array(new ArrayBuffer(size)); |
| 1003 | } else { | |
| 1004 | 0 | buffer = new Array(size); |
| 1005 | } | |
| 1006 | ||
| 1007 | // If asBuffer is false use typed arrays | |
| 1008 | 0 | BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, 0, serializeFunctions); |
| 1009 | 0 | return buffer; |
| 1010 | } | |
| 1011 | ||
| 1012 | /** | |
| 1013 | * Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5 | |
| 1014 | * | |
| 1015 | * @ignore | |
| 1016 | * @api private | |
| 1017 | */ | |
| 1018 | 1 | var functionCache = BSON.functionCache = {}; |
| 1019 | ||
| 1020 | /** | |
| 1021 | * Crc state variables shared by function | |
| 1022 | * | |
| 1023 | * @ignore | |
| 1024 | * @api private | |
| 1025 | */ | |
| 1026 | 1 | var table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D]; |
| 1027 | ||
| 1028 | /** | |
| 1029 | * CRC32 hash method, Fast and enough versitility for our usage | |
| 1030 | * | |
| 1031 | * @ignore | |
| 1032 | * @api private | |
| 1033 | */ | |
| 1034 | 1 | var crc32 = function(string, start, end) { |
| 1035 | 0 | var crc = 0 |
| 1036 | 0 | var x = 0; |
| 1037 | 0 | var y = 0; |
| 1038 | 0 | crc = crc ^ (-1); |
| 1039 | ||
| 1040 | 0 | for(var i = start, iTop = end; i < iTop;i++) { |
| 1041 | 0 | y = (crc ^ string[i]) & 0xFF; |
| 1042 | 0 | x = table[y]; |
| 1043 | 0 | crc = (crc >>> 8) ^ x; |
| 1044 | } | |
| 1045 | ||
| 1046 | 0 | return crc ^ (-1); |
| 1047 | } | |
| 1048 | ||
| 1049 | /** | |
| 1050 | * Deserialize stream data as BSON documents. | |
| 1051 | * | |
| 1052 | * Options | |
| 1053 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1054 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1055 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1056 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 1057 | * | |
| 1058 | * @param {Buffer} data the buffer containing the serialized set of BSON documents. | |
| 1059 | * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. | |
| 1060 | * @param {Number} numberOfDocuments number of documents to deserialize. | |
| 1061 | * @param {Array} documents an array where to store the deserialized documents. | |
| 1062 | * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. | |
| 1063 | * @param {Object} [options] additional options used for the deserialization. | |
| 1064 | * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. | |
| 1065 | * @api public | |
| 1066 | */ | |
| 1067 | 1 | BSON.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { |
| 1068 | // if(numberOfDocuments !== documents.length) throw new Error("Number of expected results back is less than the number of documents"); | |
| 1069 | 0 | options = options != null ? options : {}; |
| 1070 | 0 | var index = startIndex; |
| 1071 | // Loop over all documents | |
| 1072 | 0 | for(var i = 0; i < numberOfDocuments; i++) { |
| 1073 | // Find size of the document | |
| 1074 | 0 | var size = data[index] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24; |
| 1075 | // Update options with index | |
| 1076 | 0 | options['index'] = index; |
| 1077 | // Parse the document at this point | |
| 1078 | 0 | documents[docStartIndex + i] = BSON.deserialize(data, options); |
| 1079 | // Adjust index by the document size | |
| 1080 | 0 | index = index + size; |
| 1081 | } | |
| 1082 | ||
| 1083 | // Return object containing end index of parsing and list of documents | |
| 1084 | 0 | return index; |
| 1085 | } | |
| 1086 | ||
| 1087 | /** | |
| 1088 | * Ensure eval is isolated. | |
| 1089 | * | |
| 1090 | * @ignore | |
| 1091 | * @api private | |
| 1092 | */ | |
| 1093 | 1 | var isolateEvalWithHash = function(functionCache, hash, functionString, object) { |
| 1094 | // Contains the value we are going to set | |
| 1095 | 0 | var value = null; |
| 1096 | ||
| 1097 | // Check for cache hit, eval if missing and return cached function | |
| 1098 | 0 | if(functionCache[hash] == null) { |
| 1099 | 0 | eval("value = " + functionString); |
| 1100 | 0 | functionCache[hash] = value; |
| 1101 | } | |
| 1102 | // Set the object | |
| 1103 | 0 | return functionCache[hash].bind(object); |
| 1104 | } | |
| 1105 | ||
| 1106 | /** | |
| 1107 | * Ensure eval is isolated. | |
| 1108 | * | |
| 1109 | * @ignore | |
| 1110 | * @api private | |
| 1111 | */ | |
| 1112 | 1 | var isolateEval = function(functionString) { |
| 1113 | // Contains the value we are going to set | |
| 1114 | 0 | var value = null; |
| 1115 | // Eval the function | |
| 1116 | 0 | eval("value = " + functionString); |
| 1117 | 0 | return value; |
| 1118 | } | |
| 1119 | ||
| 1120 | /** | |
| 1121 | * Convert Uint8Array to String | |
| 1122 | * | |
| 1123 | * @ignore | |
| 1124 | * @api private | |
| 1125 | */ | |
| 1126 | 1 | var convertUint8ArrayToUtf8String = function(byteArray, startIndex, endIndex) { |
| 1127 | 0 | return BinaryParser.decode_utf8(convertArraytoUtf8BinaryString(byteArray, startIndex, endIndex)); |
| 1128 | } | |
| 1129 | ||
| 1130 | 1 | var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { |
| 1131 | 0 | var result = ""; |
| 1132 | 0 | for(var i = startIndex; i < endIndex; i++) { |
| 1133 | 0 | result = result + String.fromCharCode(byteArray[i]); |
| 1134 | } | |
| 1135 | ||
| 1136 | 0 | return result; |
| 1137 | }; | |
| 1138 | ||
| 1139 | /** | |
| 1140 | * Deserialize data as BSON. | |
| 1141 | * | |
| 1142 | * Options | |
| 1143 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1144 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1145 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1146 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 1147 | * | |
| 1148 | * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. | |
| 1149 | * @param {Object} [options] additional options used for the deserialization. | |
| 1150 | * @param {Boolean} [isArray] ignore used for recursive parsing. | |
| 1151 | * @return {Object} returns the deserialized Javascript Object. | |
| 1152 | * @api public | |
| 1153 | */ | |
| 1154 | 1 | BSON.deserialize = function(buffer, options, isArray) { |
| 1155 | // Options | |
| 1156 | 0 | options = options == null ? {} : options; |
| 1157 | 0 | var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; |
| 1158 | 0 | var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; |
| 1159 | 0 | var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32']; |
| 1160 | 0 | var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; |
| 1161 | ||
| 1162 | // Validate that we have at least 4 bytes of buffer | |
| 1163 | 0 | if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long"); |
| 1164 | ||
| 1165 | // Set up index | |
| 1166 | 0 | var index = typeof options['index'] == 'number' ? options['index'] : 0; |
| 1167 | // Reads in a C style string | |
| 1168 | 0 | var readCStyleString = function() { |
| 1169 | // Get the start search index | |
| 1170 | 0 | var i = index; |
| 1171 | // Locate the end of the c string | |
| 1172 | 0 | while(buffer[i] !== 0x00 && i < buffer.length) { |
| 1173 | 0 | i++ |
| 1174 | } | |
| 1175 | // If are at the end of the buffer there is a problem with the document | |
| 1176 | 0 | if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString") |
| 1177 | // Grab utf8 encoded string | |
| 1178 | 0 | var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, i) : convertUint8ArrayToUtf8String(buffer, index, i); |
| 1179 | // Update index position | |
| 1180 | 0 | index = i + 1; |
| 1181 | // Return string | |
| 1182 | 0 | return string; |
| 1183 | } | |
| 1184 | ||
| 1185 | // Create holding object | |
| 1186 | 0 | var object = isArray ? [] : {}; |
| 1187 | ||
| 1188 | // Read the document size | |
| 1189 | 0 | var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1190 | ||
| 1191 | // Ensure buffer is valid size | |
| 1192 | 0 | if(size < 5 || size > buffer.length) throw new Error("corrupt bson message"); |
| 1193 | ||
| 1194 | // While we have more left data left keep parsing | |
| 1195 | 0 | while(true) { |
| 1196 | // Read the type | |
| 1197 | 0 | var elementType = buffer[index++]; |
| 1198 | // If we get a zero it's the last byte, exit | |
| 1199 | 0 | if(elementType == 0) break; |
| 1200 | // Read the name of the field | |
| 1201 | 0 | var name = readCStyleString(); |
| 1202 | // Switch on the type | |
| 1203 | 0 | switch(elementType) { |
| 1204 | case BSON.BSON_DATA_OID: | |
| 1205 | 0 | var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('binary', index, index + 12) : convertArraytoUtf8BinaryString(buffer, index, index + 12); |
| 1206 | // Decode the oid | |
| 1207 | 0 | object[name] = new ObjectID(string); |
| 1208 | // Update index | |
| 1209 | 0 | index = index + 12; |
| 1210 | 0 | break; |
| 1211 | case BSON.BSON_DATA_STRING: | |
| 1212 | // Read the content of the field | |
| 1213 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1214 | // Add string to object | |
| 1215 | 0 | object[name] = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1216 | // Update parse index position | |
| 1217 | 0 | index = index + stringSize; |
| 1218 | 0 | break; |
| 1219 | case BSON.BSON_DATA_INT: | |
| 1220 | // Decode the 32bit value | |
| 1221 | 0 | object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1222 | 0 | break; |
| 1223 | case BSON.BSON_DATA_NUMBER: | |
| 1224 | // Decode the double value | |
| 1225 | 0 | object[name] = readIEEE754(buffer, index, 'little', 52, 8); |
| 1226 | // Update the index | |
| 1227 | 0 | index = index + 8; |
| 1228 | 0 | break; |
| 1229 | case BSON.BSON_DATA_DATE: | |
| 1230 | // Unpack the low and high bits | |
| 1231 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1232 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1233 | // Set date object | |
| 1234 | 0 | object[name] = new Date(new Long(lowBits, highBits).toNumber()); |
| 1235 | 0 | break; |
| 1236 | case BSON.BSON_DATA_BOOLEAN: | |
| 1237 | // Parse the boolean value | |
| 1238 | 0 | object[name] = buffer[index++] == 1; |
| 1239 | 0 | break; |
| 1240 | case BSON.BSON_DATA_NULL: | |
| 1241 | // Parse the boolean value | |
| 1242 | 0 | object[name] = null; |
| 1243 | 0 | break; |
| 1244 | case BSON.BSON_DATA_BINARY: | |
| 1245 | // Decode the size of the binary blob | |
| 1246 | 0 | var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1247 | // Decode the subtype | |
| 1248 | 0 | var subType = buffer[index++]; |
| 1249 | // Decode as raw Buffer object if options specifies it | |
| 1250 | 0 | if(buffer['slice'] != null) { |
| 1251 | // If we have subtype 2 skip the 4 bytes for the size | |
| 1252 | 0 | if(subType == Binary.SUBTYPE_BYTE_ARRAY) { |
| 1253 | 0 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1254 | } | |
| 1255 | // Slice the data | |
| 1256 | 0 | object[name] = new Binary(buffer.slice(index, index + binarySize), subType); |
| 1257 | } else { | |
| 1258 | 0 | var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize); |
| 1259 | // If we have subtype 2 skip the 4 bytes for the size | |
| 1260 | 0 | if(subType == Binary.SUBTYPE_BYTE_ARRAY) { |
| 1261 | 0 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1262 | } | |
| 1263 | // Copy the data | |
| 1264 | 0 | for(var i = 0; i < binarySize; i++) { |
| 1265 | 0 | _buffer[i] = buffer[index + i]; |
| 1266 | } | |
| 1267 | // Create the binary object | |
| 1268 | 0 | object[name] = new Binary(_buffer, subType); |
| 1269 | } | |
| 1270 | // Update the index | |
| 1271 | 0 | index = index + binarySize; |
| 1272 | 0 | break; |
| 1273 | case BSON.BSON_DATA_ARRAY: | |
| 1274 | 0 | options['index'] = index; |
| 1275 | // Decode the size of the array document | |
| 1276 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1277 | // Set the array to the object | |
| 1278 | 0 | object[name] = BSON.deserialize(buffer, options, true); |
| 1279 | // Adjust the index | |
| 1280 | 0 | index = index + objectSize; |
| 1281 | 0 | break; |
| 1282 | case BSON.BSON_DATA_OBJECT: | |
| 1283 | 0 | options['index'] = index; |
| 1284 | // Decode the size of the object document | |
| 1285 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1286 | // Set the array to the object | |
| 1287 | 0 | object[name] = BSON.deserialize(buffer, options, false); |
| 1288 | // Adjust the index | |
| 1289 | 0 | index = index + objectSize; |
| 1290 | 0 | break; |
| 1291 | case BSON.BSON_DATA_REGEXP: | |
| 1292 | // Create the regexp | |
| 1293 | 0 | var source = readCStyleString(); |
| 1294 | 0 | var regExpOptions = readCStyleString(); |
| 1295 | // For each option add the corresponding one for javascript | |
| 1296 | 0 | var optionsArray = new Array(regExpOptions.length); |
| 1297 | ||
| 1298 | // Parse options | |
| 1299 | 0 | for(var i = 0; i < regExpOptions.length; i++) { |
| 1300 | 0 | switch(regExpOptions[i]) { |
| 1301 | case 'm': | |
| 1302 | 0 | optionsArray[i] = 'm'; |
| 1303 | 0 | break; |
| 1304 | case 's': | |
| 1305 | 0 | optionsArray[i] = 'g'; |
| 1306 | 0 | break; |
| 1307 | case 'i': | |
| 1308 | 0 | optionsArray[i] = 'i'; |
| 1309 | 0 | break; |
| 1310 | } | |
| 1311 | } | |
| 1312 | ||
| 1313 | 0 | object[name] = new RegExp(source, optionsArray.join('')); |
| 1314 | 0 | break; |
| 1315 | case BSON.BSON_DATA_LONG: | |
| 1316 | // Unpack the low and high bits | |
| 1317 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1318 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1319 | // Create long object | |
| 1320 | 0 | var long = new Long(lowBits, highBits); |
| 1321 | // Promote the long if possible | |
| 1322 | 0 | if(promoteLongs) { |
| 1323 | 0 | object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long; |
| 1324 | } else { | |
| 1325 | 0 | object[name] = long; |
| 1326 | } | |
| 1327 | 0 | break; |
| 1328 | case BSON.BSON_DATA_SYMBOL: | |
| 1329 | // Read the content of the field | |
| 1330 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1331 | // Add string to object | |
| 1332 | 0 | object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1)); |
| 1333 | // Update parse index position | |
| 1334 | 0 | index = index + stringSize; |
| 1335 | 0 | break; |
| 1336 | case BSON.BSON_DATA_TIMESTAMP: | |
| 1337 | // Unpack the low and high bits | |
| 1338 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1339 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1340 | // Set the object | |
| 1341 | 0 | object[name] = new Timestamp(lowBits, highBits); |
| 1342 | 0 | break; |
| 1343 | case BSON.BSON_DATA_MIN_KEY: | |
| 1344 | // Parse the object | |
| 1345 | 0 | object[name] = new MinKey(); |
| 1346 | 0 | break; |
| 1347 | case BSON.BSON_DATA_MAX_KEY: | |
| 1348 | // Parse the object | |
| 1349 | 0 | object[name] = new MaxKey(); |
| 1350 | 0 | break; |
| 1351 | case BSON.BSON_DATA_CODE: | |
| 1352 | // Read the content of the field | |
| 1353 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1354 | // Function string | |
| 1355 | 0 | var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1356 | ||
| 1357 | // If we are evaluating the functions | |
| 1358 | 0 | if(evalFunctions) { |
| 1359 | // Contains the value we are going to set | |
| 1360 | 0 | var value = null; |
| 1361 | // If we have cache enabled let's look for the md5 of the function in the cache | |
| 1362 | 0 | if(cacheFunctions) { |
| 1363 | 0 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
| 1364 | // Got to do this to avoid V8 deoptimizing the call due to finding eval | |
| 1365 | 0 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
| 1366 | } else { | |
| 1367 | // Set directly | |
| 1368 | 0 | object[name] = isolateEval(functionString); |
| 1369 | } | |
| 1370 | } else { | |
| 1371 | 0 | object[name] = new Code(functionString, {}); |
| 1372 | } | |
| 1373 | ||
| 1374 | // Update parse index position | |
| 1375 | 0 | index = index + stringSize; |
| 1376 | 0 | break; |
| 1377 | case BSON.BSON_DATA_CODE_W_SCOPE: | |
| 1378 | // Read the content of the field | |
| 1379 | 0 | var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1380 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1381 | // Javascript function | |
| 1382 | 0 | var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1383 | // Update parse index position | |
| 1384 | 0 | index = index + stringSize; |
| 1385 | // Parse the element | |
| 1386 | 0 | options['index'] = index; |
| 1387 | // Decode the size of the object document | |
| 1388 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1389 | // Decode the scope object | |
| 1390 | 0 | var scopeObject = BSON.deserialize(buffer, options, false); |
| 1391 | // Adjust the index | |
| 1392 | 0 | index = index + objectSize; |
| 1393 | ||
| 1394 | // If we are evaluating the functions | |
| 1395 | 0 | if(evalFunctions) { |
| 1396 | // Contains the value we are going to set | |
| 1397 | 0 | var value = null; |
| 1398 | // If we have cache enabled let's look for the md5 of the function in the cache | |
| 1399 | 0 | if(cacheFunctions) { |
| 1400 | 0 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
| 1401 | // Got to do this to avoid V8 deoptimizing the call due to finding eval | |
| 1402 | 0 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
| 1403 | } else { | |
| 1404 | // Set directly | |
| 1405 | 0 | object[name] = isolateEval(functionString); |
| 1406 | } | |
| 1407 | ||
| 1408 | // Set the scope on the object | |
| 1409 | 0 | object[name].scope = scopeObject; |
| 1410 | } else { | |
| 1411 | 0 | object[name] = new Code(functionString, scopeObject); |
| 1412 | } | |
| 1413 | ||
| 1414 | // Add string to object | |
| 1415 | 0 | break; |
| 1416 | } | |
| 1417 | } | |
| 1418 | ||
| 1419 | // Check if we have a db ref object | |
| 1420 | 0 | if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); |
| 1421 | ||
| 1422 | // Return the final objects | |
| 1423 | 0 | return object; |
| 1424 | } | |
| 1425 | ||
| 1426 | /** | |
| 1427 | * Check if key name is valid. | |
| 1428 | * | |
| 1429 | * @ignore | |
| 1430 | * @api private | |
| 1431 | */ | |
| 1432 | 1 | BSON.checkKey = function checkKey (key, dollarsAndDotsOk) { |
| 1433 | 0 | if (!key.length) return; |
| 1434 | // Check if we have a legal key for the object | |
| 1435 | 0 | if (!!~key.indexOf("\x00")) { |
| 1436 | // The BSON spec doesn't allow keys with null bytes because keys are | |
| 1437 | // null-terminated. | |
| 1438 | 0 | throw Error("key " + key + " must not contain null bytes"); |
| 1439 | } | |
| 1440 | 0 | if (!dollarsAndDotsOk) { |
| 1441 | 0 | if('$' == key[0]) { |
| 1442 | 0 | throw Error("key " + key + " must not start with '$'"); |
| 1443 | 0 | } else if (!!~key.indexOf('.')) { |
| 1444 | 0 | throw Error("key " + key + " must not contain '.'"); |
| 1445 | } | |
| 1446 | } | |
| 1447 | }; | |
| 1448 | ||
| 1449 | /** | |
| 1450 | * Deserialize data as BSON. | |
| 1451 | * | |
| 1452 | * Options | |
| 1453 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1454 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1455 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1456 | * | |
| 1457 | * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. | |
| 1458 | * @param {Object} [options] additional options used for the deserialization. | |
| 1459 | * @param {Boolean} [isArray] ignore used for recursive parsing. | |
| 1460 | * @return {Object} returns the deserialized Javascript Object. | |
| 1461 | * @api public | |
| 1462 | */ | |
| 1463 | 1 | BSON.prototype.deserialize = function(data, options) { |
| 1464 | 0 | return BSON.deserialize(data, options); |
| 1465 | } | |
| 1466 | ||
| 1467 | /** | |
| 1468 | * Deserialize stream data as BSON documents. | |
| 1469 | * | |
| 1470 | * Options | |
| 1471 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1472 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1473 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1474 | * | |
| 1475 | * @param {Buffer} data the buffer containing the serialized set of BSON documents. | |
| 1476 | * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. | |
| 1477 | * @param {Number} numberOfDocuments number of documents to deserialize. | |
| 1478 | * @param {Array} documents an array where to store the deserialized documents. | |
| 1479 | * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. | |
| 1480 | * @param {Object} [options] additional options used for the deserialization. | |
| 1481 | * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. | |
| 1482 | * @api public | |
| 1483 | */ | |
| 1484 | 1 | BSON.prototype.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { |
| 1485 | 0 | return BSON.deserializeStream(data, startIndex, numberOfDocuments, documents, docStartIndex, options); |
| 1486 | } | |
| 1487 | ||
| 1488 | /** | |
| 1489 | * Serialize a Javascript object. | |
| 1490 | * | |
| 1491 | * @param {Object} object the Javascript object to serialize. | |
| 1492 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 1493 | * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. | |
| 1494 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 1495 | * @return {Buffer} returns the Buffer object containing the serialized object. | |
| 1496 | * @api public | |
| 1497 | */ | |
| 1498 | 1 | BSON.prototype.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { |
| 1499 | 0 | return BSON.serialize(object, checkKeys, asBuffer, serializeFunctions); |
| 1500 | } | |
| 1501 | ||
| 1502 | /** | |
| 1503 | * Calculate the bson size for a passed in Javascript object. | |
| 1504 | * | |
| 1505 | * @param {Object} object the Javascript object to calculate the BSON byte size for. | |
| 1506 | * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. | |
| 1507 | * @return {Number} returns the number of bytes the BSON object will take up. | |
| 1508 | * @api public | |
| 1509 | */ | |
| 1510 | 1 | BSON.prototype.calculateObjectSize = function(object, serializeFunctions) { |
| 1511 | 0 | return BSON.calculateObjectSize(object, serializeFunctions); |
| 1512 | } | |
| 1513 | ||
| 1514 | /** | |
| 1515 | * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. | |
| 1516 | * | |
| 1517 | * @param {Object} object the Javascript object to serialize. | |
| 1518 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 1519 | * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. | |
| 1520 | * @param {Number} index the index in the buffer where we wish to start serializing into. | |
| 1521 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 1522 | * @return {Number} returns the new write index in the Buffer. | |
| 1523 | * @api public | |
| 1524 | */ | |
| 1525 | 1 | BSON.prototype.serializeWithBufferAndIndex = function(object, checkKeys, buffer, startIndex, serializeFunctions) { |
| 1526 | 0 | return BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, startIndex, serializeFunctions); |
| 1527 | } | |
| 1528 | ||
| 1529 | /** | |
| 1530 | * @ignore | |
| 1531 | * @api private | |
| 1532 | */ | |
| 1533 | 1 | exports.Code = Code; |
| 1534 | 1 | exports.Symbol = Symbol; |
| 1535 | 1 | exports.BSON = BSON; |
| 1536 | 1 | exports.DBRef = DBRef; |
| 1537 | 1 | exports.Binary = Binary; |
| 1538 | 1 | exports.ObjectID = ObjectID; |
| 1539 | 1 | exports.Long = Long; |
| 1540 | 1 | exports.Timestamp = Timestamp; |
| 1541 | 1 | exports.Double = Double; |
| 1542 | 1 | exports.MinKey = MinKey; |
| 1543 | 1 | exports.MaxKey = MaxKey; |
| 1544 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Code type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Code type. | |
| 5 | * @param {String|Function} code a string or function. | |
| 6 | * @param {Object} [scope] an optional scope for the function. | |
| 7 | * @return {Code} | |
| 8 | */ | |
| 9 | 1 | function Code(code, scope) { |
| 10 | 0 | if(!(this instanceof Code)) return new Code(code, scope); |
| 11 | ||
| 12 | 0 | this._bsontype = 'Code'; |
| 13 | 0 | this.code = code; |
| 14 | 0 | this.scope = scope == null ? {} : scope; |
| 15 | }; | |
| 16 | ||
| 17 | /** | |
| 18 | * @ignore | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | 1 | Code.prototype.toJSON = function() { |
| 22 | 0 | return {scope:this.scope, code:this.code}; |
| 23 | } | |
| 24 | ||
| 25 | 1 | exports.Code = Code; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON DBRef type. | |
| 3 | * | |
| 4 | * @class Represents the BSON DBRef type. | |
| 5 | * @param {String} namespace the collection name. | |
| 6 | * @param {ObjectID} oid the reference ObjectID. | |
| 7 | * @param {String} [db] optional db name, if omitted the reference is local to the current db. | |
| 8 | * @return {DBRef} | |
| 9 | */ | |
| 10 | 1 | function DBRef(namespace, oid, db) { |
| 11 | 0 | if(!(this instanceof DBRef)) return new DBRef(namespace, oid, db); |
| 12 | ||
| 13 | 0 | this._bsontype = 'DBRef'; |
| 14 | 0 | this.namespace = namespace; |
| 15 | 0 | this.oid = oid; |
| 16 | 0 | this.db = db; |
| 17 | }; | |
| 18 | ||
| 19 | /** | |
| 20 | * @ignore | |
| 21 | * @api private | |
| 22 | */ | |
| 23 | 1 | DBRef.prototype.toJSON = function() { |
| 24 | 0 | return { |
| 25 | '$ref':this.namespace, | |
| 26 | '$id':this.oid, | |
| 27 | '$db':this.db == null ? '' : this.db | |
| 28 | }; | |
| 29 | } | |
| 30 | ||
| 31 | 1 | exports.DBRef = DBRef; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Double type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Double type. | |
| 5 | * @param {Number} value the number we want to represent as a double. | |
| 6 | * @return {Double} | |
| 7 | */ | |
| 8 | 1 | function Double(value) { |
| 9 | 0 | if(!(this instanceof Double)) return new Double(value); |
| 10 | ||
| 11 | 0 | this._bsontype = 'Double'; |
| 12 | 0 | this.value = value; |
| 13 | } | |
| 14 | ||
| 15 | /** | |
| 16 | * Access the number value. | |
| 17 | * | |
| 18 | * @return {Number} returns the wrapped double number. | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | 1 | Double.prototype.valueOf = function() { |
| 22 | 0 | return this.value; |
| 23 | }; | |
| 24 | ||
| 25 | /** | |
| 26 | * @ignore | |
| 27 | * @api private | |
| 28 | */ | |
| 29 | 1 | Double.prototype.toJSON = function() { |
| 30 | 0 | return this.value; |
| 31 | } | |
| 32 | ||
| 33 | 1 | exports.Double = Double; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Copyright (c) 2008, Fair Oaks Labs, Inc. | |
| 2 | // All rights reserved. | |
| 3 | // | |
| 4 | // Redistribution and use in source and binary forms, with or without | |
| 5 | // modification, are permitted provided that the following conditions are met: | |
| 6 | // | |
| 7 | // * Redistributions of source code must retain the above copyright notice, | |
| 8 | // this list of conditions and the following disclaimer. | |
| 9 | // | |
| 10 | // * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | // this list of conditions and the following disclaimer in the documentation | |
| 12 | // and/or other materials provided with the distribution. | |
| 13 | // | |
| 14 | // * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors | |
| 15 | // may be used to endorse or promote products derived from this software | |
| 16 | // without specific prior written permission. | |
| 17 | // | |
| 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| 22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| 23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| 24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| 25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| 26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| 27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| 28 | // POSSIBILITY OF SUCH DAMAGE. | |
| 29 | // | |
| 30 | // | |
| 31 | // Modifications to writeIEEE754 to support negative zeroes made by Brian White | |
| 32 | ||
| 33 | 1 | var readIEEE754 = function(buffer, offset, endian, mLen, nBytes) { |
| 34 | 0 | var e, m, |
| 35 | bBE = (endian === 'big'), | |
| 36 | eLen = nBytes * 8 - mLen - 1, | |
| 37 | eMax = (1 << eLen) - 1, | |
| 38 | eBias = eMax >> 1, | |
| 39 | nBits = -7, | |
| 40 | i = bBE ? 0 : (nBytes - 1), | |
| 41 | d = bBE ? 1 : -1, | |
| 42 | s = buffer[offset + i]; | |
| 43 | ||
| 44 | 0 | i += d; |
| 45 | ||
| 46 | 0 | e = s & ((1 << (-nBits)) - 1); |
| 47 | 0 | s >>= (-nBits); |
| 48 | 0 | nBits += eLen; |
| 49 | 0 | for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); |
| 50 | ||
| 51 | 0 | m = e & ((1 << (-nBits)) - 1); |
| 52 | 0 | e >>= (-nBits); |
| 53 | 0 | nBits += mLen; |
| 54 | 0 | for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); |
| 55 | ||
| 56 | 0 | if (e === 0) { |
| 57 | 0 | e = 1 - eBias; |
| 58 | 0 | } else if (e === eMax) { |
| 59 | 0 | return m ? NaN : ((s ? -1 : 1) * Infinity); |
| 60 | } else { | |
| 61 | 0 | m = m + Math.pow(2, mLen); |
| 62 | 0 | e = e - eBias; |
| 63 | } | |
| 64 | 0 | return (s ? -1 : 1) * m * Math.pow(2, e - mLen); |
| 65 | }; | |
| 66 | ||
| 67 | 1 | var writeIEEE754 = function(buffer, value, offset, endian, mLen, nBytes) { |
| 68 | 0 | var e, m, c, |
| 69 | bBE = (endian === 'big'), | |
| 70 | eLen = nBytes * 8 - mLen - 1, | |
| 71 | eMax = (1 << eLen) - 1, | |
| 72 | eBias = eMax >> 1, | |
| 73 | rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), | |
| 74 | i = bBE ? (nBytes-1) : 0, | |
| 75 | d = bBE ? -1 : 1, | |
| 76 | s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; | |
| 77 | ||
| 78 | 0 | value = Math.abs(value); |
| 79 | ||
| 80 | 0 | if (isNaN(value) || value === Infinity) { |
| 81 | 0 | m = isNaN(value) ? 1 : 0; |
| 82 | 0 | e = eMax; |
| 83 | } else { | |
| 84 | 0 | e = Math.floor(Math.log(value) / Math.LN2); |
| 85 | 0 | if (value * (c = Math.pow(2, -e)) < 1) { |
| 86 | 0 | e--; |
| 87 | 0 | c *= 2; |
| 88 | } | |
| 89 | 0 | if (e+eBias >= 1) { |
| 90 | 0 | value += rt / c; |
| 91 | } else { | |
| 92 | 0 | value += rt * Math.pow(2, 1 - eBias); |
| 93 | } | |
| 94 | 0 | if (value * c >= 2) { |
| 95 | 0 | e++; |
| 96 | 0 | c /= 2; |
| 97 | } | |
| 98 | ||
| 99 | 0 | if (e + eBias >= eMax) { |
| 100 | 0 | m = 0; |
| 101 | 0 | e = eMax; |
| 102 | 0 | } else if (e + eBias >= 1) { |
| 103 | 0 | m = (value * c - 1) * Math.pow(2, mLen); |
| 104 | 0 | e = e + eBias; |
| 105 | } else { | |
| 106 | 0 | m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); |
| 107 | 0 | e = 0; |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | 0 | for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); |
| 112 | ||
| 113 | 0 | e = (e << mLen) | m; |
| 114 | 0 | eLen += mLen; |
| 115 | 0 | for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); |
| 116 | ||
| 117 | 0 | buffer[offset + i - d] |= s * 128; |
| 118 | }; | |
| 119 | ||
| 120 | 1 | exports.readIEEE754 = readIEEE754; |
| 121 | 1 | exports.writeIEEE754 = writeIEEE754; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | try { |
| 2 | 1 | exports.BSONPure = require('./bson'); |
| 3 | 1 | exports.BSONNative = require('../../ext'); |
| 4 | } catch(err) { | |
| 5 | // do nothing | |
| 6 | } | |
| 7 | ||
| 8 | 1 | [ './binary_parser' |
| 9 | , './binary' | |
| 10 | , './code' | |
| 11 | , './db_ref' | |
| 12 | , './double' | |
| 13 | , './max_key' | |
| 14 | , './min_key' | |
| 15 | , './objectid' | |
| 16 | , './symbol' | |
| 17 | , './timestamp' | |
| 18 | , './long'].forEach(function (path) { | |
| 19 | 11 | var module = require('./' + path); |
| 20 | 11 | for (var i in module) { |
| 21 | 12 | exports[i] = module[i]; |
| 22 | } | |
| 23 | }); | |
| 24 | ||
| 25 | // Exports all the classes for the NATIVE JS BSON Parser | |
| 26 | 1 | exports.native = function() { |
| 27 | 0 | var classes = {}; |
| 28 | // Map all the classes | |
| 29 | 0 | [ './binary_parser' |
| 30 | , './binary' | |
| 31 | , './code' | |
| 32 | , './db_ref' | |
| 33 | , './double' | |
| 34 | , './max_key' | |
| 35 | , './min_key' | |
| 36 | , './objectid' | |
| 37 | , './symbol' | |
| 38 | , './timestamp' | |
| 39 | , './long' | |
| 40 | , '../../ext' | |
| 41 | ].forEach(function (path) { | |
| 42 | 0 | var module = require('./' + path); |
| 43 | 0 | for (var i in module) { |
| 44 | 0 | classes[i] = module[i]; |
| 45 | } | |
| 46 | }); | |
| 47 | // Return classes list | |
| 48 | 0 | return classes; |
| 49 | } | |
| 50 | ||
| 51 | // Exports all the classes for the PURE JS BSON Parser | |
| 52 | 1 | exports.pure = function() { |
| 53 | 0 | var classes = {}; |
| 54 | // Map all the classes | |
| 55 | 0 | [ './binary_parser' |
| 56 | , './binary' | |
| 57 | , './code' | |
| 58 | , './db_ref' | |
| 59 | , './double' | |
| 60 | , './max_key' | |
| 61 | , './min_key' | |
| 62 | , './objectid' | |
| 63 | , './symbol' | |
| 64 | , './timestamp' | |
| 65 | , './long' | |
| 66 | , '././bson'].forEach(function (path) { | |
| 67 | 0 | var module = require('./' + path); |
| 68 | 0 | for (var i in module) { |
| 69 | 0 | classes[i] = module[i]; |
| 70 | } | |
| 71 | }); | |
| 72 | // Return classes list | |
| 73 | 0 | return classes; |
| 74 | } | |
| 75 |
| Line | Hits | Source |
|---|---|---|
| 1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 2 | // you may not use this file except in compliance with the License. | |
| 3 | // You may obtain a copy of the License at | |
| 4 | // | |
| 5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
| 6 | // | |
| 7 | // Unless required by applicable law or agreed to in writing, software | |
| 8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
| 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 10 | // See the License for the specific language governing permissions and | |
| 11 | // limitations under the License. | |
| 12 | // | |
| 13 | // Copyright 2009 Google Inc. All Rights Reserved | |
| 14 | ||
| 15 | /** | |
| 16 | * Defines a Long class for representing a 64-bit two's-complement | |
| 17 | * integer value, which faithfully simulates the behavior of a Java "Long". This | |
| 18 | * implementation is derived from LongLib in GWT. | |
| 19 | * | |
| 20 | * Constructs a 64-bit two's-complement integer, given its low and high 32-bit | |
| 21 | * values as *signed* integers. See the from* functions below for more | |
| 22 | * convenient ways of constructing Longs. | |
| 23 | * | |
| 24 | * The internal representation of a Long is the two given signed, 32-bit values. | |
| 25 | * We use 32-bit pieces because these are the size of integers on which | |
| 26 | * Javascript performs bit-operations. For operations like addition and | |
| 27 | * multiplication, we split each number into 16-bit pieces, which can easily be | |
| 28 | * multiplied within Javascript's floating-point representation without overflow | |
| 29 | * or change in sign. | |
| 30 | * | |
| 31 | * In the algorithms below, we frequently reduce the negative case to the | |
| 32 | * positive case by negating the input(s) and then post-processing the result. | |
| 33 | * Note that we must ALWAYS check specially whether those values are MIN_VALUE | |
| 34 | * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as | |
| 35 | * a positive number, it overflows back into a negative). Not handling this | |
| 36 | * case would often result in infinite recursion. | |
| 37 | * | |
| 38 | * @class Represents the BSON Long type. | |
| 39 | * @param {Number} low the low (signed) 32 bits of the Long. | |
| 40 | * @param {Number} high the high (signed) 32 bits of the Long. | |
| 41 | */ | |
| 42 | 1 | function Long(low, high) { |
| 43 | 10 | if(!(this instanceof Long)) return new Long(low, high); |
| 44 | ||
| 45 | 10 | this._bsontype = 'Long'; |
| 46 | /** | |
| 47 | * @type {number} | |
| 48 | * @api private | |
| 49 | */ | |
| 50 | 10 | this.low_ = low | 0; // force into 32 signed bits. |
| 51 | ||
| 52 | /** | |
| 53 | * @type {number} | |
| 54 | * @api private | |
| 55 | */ | |
| 56 | 10 | this.high_ = high | 0; // force into 32 signed bits. |
| 57 | }; | |
| 58 | ||
| 59 | /** | |
| 60 | * Return the int value. | |
| 61 | * | |
| 62 | * @return {Number} the value, assuming it is a 32-bit integer. | |
| 63 | * @api public | |
| 64 | */ | |
| 65 | 1 | Long.prototype.toInt = function() { |
| 66 | 0 | return this.low_; |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Return the Number value. | |
| 71 | * | |
| 72 | * @return {Number} the closest floating-point representation to this value. | |
| 73 | * @api public | |
| 74 | */ | |
| 75 | 1 | Long.prototype.toNumber = function() { |
| 76 | 0 | return this.high_ * Long.TWO_PWR_32_DBL_ + |
| 77 | this.getLowBitsUnsigned(); | |
| 78 | }; | |
| 79 | ||
| 80 | /** | |
| 81 | * Return the JSON value. | |
| 82 | * | |
| 83 | * @return {String} the JSON representation. | |
| 84 | * @api public | |
| 85 | */ | |
| 86 | 1 | Long.prototype.toJSON = function() { |
| 87 | 0 | return this.toString(); |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Return the String value. | |
| 92 | * | |
| 93 | * @param {Number} [opt_radix] the radix in which the text should be written. | |
| 94 | * @return {String} the textual representation of this value. | |
| 95 | * @api public | |
| 96 | */ | |
| 97 | 1 | Long.prototype.toString = function(opt_radix) { |
| 98 | 0 | var radix = opt_radix || 10; |
| 99 | 0 | if (radix < 2 || 36 < radix) { |
| 100 | 0 | throw Error('radix out of range: ' + radix); |
| 101 | } | |
| 102 | ||
| 103 | 0 | if (this.isZero()) { |
| 104 | 0 | return '0'; |
| 105 | } | |
| 106 | ||
| 107 | 0 | if (this.isNegative()) { |
| 108 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 109 | // We need to change the Long value before it can be negated, so we remove | |
| 110 | // the bottom-most digit in this base and then recurse to do the rest. | |
| 111 | 0 | var radixLong = Long.fromNumber(radix); |
| 112 | 0 | var div = this.div(radixLong); |
| 113 | 0 | var rem = div.multiply(radixLong).subtract(this); |
| 114 | 0 | return div.toString(radix) + rem.toInt().toString(radix); |
| 115 | } else { | |
| 116 | 0 | return '-' + this.negate().toString(radix); |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | // Do several (6) digits each time through the loop, so as to | |
| 121 | // minimize the calls to the very expensive emulated div. | |
| 122 | 0 | var radixToPower = Long.fromNumber(Math.pow(radix, 6)); |
| 123 | ||
| 124 | 0 | var rem = this; |
| 125 | 0 | var result = ''; |
| 126 | 0 | while (true) { |
| 127 | 0 | var remDiv = rem.div(radixToPower); |
| 128 | 0 | var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); |
| 129 | 0 | var digits = intval.toString(radix); |
| 130 | ||
| 131 | 0 | rem = remDiv; |
| 132 | 0 | if (rem.isZero()) { |
| 133 | 0 | return digits + result; |
| 134 | } else { | |
| 135 | 0 | while (digits.length < 6) { |
| 136 | 0 | digits = '0' + digits; |
| 137 | } | |
| 138 | 0 | result = '' + digits + result; |
| 139 | } | |
| 140 | } | |
| 141 | }; | |
| 142 | ||
| 143 | /** | |
| 144 | * Return the high 32-bits value. | |
| 145 | * | |
| 146 | * @return {Number} the high 32-bits as a signed value. | |
| 147 | * @api public | |
| 148 | */ | |
| 149 | 1 | Long.prototype.getHighBits = function() { |
| 150 | 0 | return this.high_; |
| 151 | }; | |
| 152 | ||
| 153 | /** | |
| 154 | * Return the low 32-bits value. | |
| 155 | * | |
| 156 | * @return {Number} the low 32-bits as a signed value. | |
| 157 | * @api public | |
| 158 | */ | |
| 159 | 1 | Long.prototype.getLowBits = function() { |
| 160 | 0 | return this.low_; |
| 161 | }; | |
| 162 | ||
| 163 | /** | |
| 164 | * Return the low unsigned 32-bits value. | |
| 165 | * | |
| 166 | * @return {Number} the low 32-bits as an unsigned value. | |
| 167 | * @api public | |
| 168 | */ | |
| 169 | 1 | Long.prototype.getLowBitsUnsigned = function() { |
| 170 | 0 | return (this.low_ >= 0) ? |
| 171 | this.low_ : Long.TWO_PWR_32_DBL_ + this.low_; | |
| 172 | }; | |
| 173 | ||
| 174 | /** | |
| 175 | * Returns the number of bits needed to represent the absolute value of this Long. | |
| 176 | * | |
| 177 | * @return {Number} Returns the number of bits needed to represent the absolute value of this Long. | |
| 178 | * @api public | |
| 179 | */ | |
| 180 | 1 | Long.prototype.getNumBitsAbs = function() { |
| 181 | 0 | if (this.isNegative()) { |
| 182 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 183 | 0 | return 64; |
| 184 | } else { | |
| 185 | 0 | return this.negate().getNumBitsAbs(); |
| 186 | } | |
| 187 | } else { | |
| 188 | 0 | var val = this.high_ != 0 ? this.high_ : this.low_; |
| 189 | 0 | for (var bit = 31; bit > 0; bit--) { |
| 190 | 0 | if ((val & (1 << bit)) != 0) { |
| 191 | 0 | break; |
| 192 | } | |
| 193 | } | |
| 194 | 0 | return this.high_ != 0 ? bit + 33 : bit + 1; |
| 195 | } | |
| 196 | }; | |
| 197 | ||
| 198 | /** | |
| 199 | * Return whether this value is zero. | |
| 200 | * | |
| 201 | * @return {Boolean} whether this value is zero. | |
| 202 | * @api public | |
| 203 | */ | |
| 204 | 1 | Long.prototype.isZero = function() { |
| 205 | 0 | return this.high_ == 0 && this.low_ == 0; |
| 206 | }; | |
| 207 | ||
| 208 | /** | |
| 209 | * Return whether this value is negative. | |
| 210 | * | |
| 211 | * @return {Boolean} whether this value is negative. | |
| 212 | * @api public | |
| 213 | */ | |
| 214 | 1 | Long.prototype.isNegative = function() { |
| 215 | 0 | return this.high_ < 0; |
| 216 | }; | |
| 217 | ||
| 218 | /** | |
| 219 | * Return whether this value is odd. | |
| 220 | * | |
| 221 | * @return {Boolean} whether this value is odd. | |
| 222 | * @api public | |
| 223 | */ | |
| 224 | 1 | Long.prototype.isOdd = function() { |
| 225 | 0 | return (this.low_ & 1) == 1; |
| 226 | }; | |
| 227 | ||
| 228 | /** | |
| 229 | * Return whether this Long equals the other | |
| 230 | * | |
| 231 | * @param {Long} other Long to compare against. | |
| 232 | * @return {Boolean} whether this Long equals the other | |
| 233 | * @api public | |
| 234 | */ | |
| 235 | 1 | Long.prototype.equals = function(other) { |
| 236 | 1 | return (this.high_ == other.high_) && (this.low_ == other.low_); |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Return whether this Long does not equal the other. | |
| 241 | * | |
| 242 | * @param {Long} other Long to compare against. | |
| 243 | * @return {Boolean} whether this Long does not equal the other. | |
| 244 | * @api public | |
| 245 | */ | |
| 246 | 1 | Long.prototype.notEquals = function(other) { |
| 247 | 0 | return (this.high_ != other.high_) || (this.low_ != other.low_); |
| 248 | }; | |
| 249 | ||
| 250 | /** | |
| 251 | * Return whether this Long is less than the other. | |
| 252 | * | |
| 253 | * @param {Long} other Long to compare against. | |
| 254 | * @return {Boolean} whether this Long is less than the other. | |
| 255 | * @api public | |
| 256 | */ | |
| 257 | 1 | Long.prototype.lessThan = function(other) { |
| 258 | 0 | return this.compare(other) < 0; |
| 259 | }; | |
| 260 | ||
| 261 | /** | |
| 262 | * Return whether this Long is less than or equal to the other. | |
| 263 | * | |
| 264 | * @param {Long} other Long to compare against. | |
| 265 | * @return {Boolean} whether this Long is less than or equal to the other. | |
| 266 | * @api public | |
| 267 | */ | |
| 268 | 1 | Long.prototype.lessThanOrEqual = function(other) { |
| 269 | 0 | return this.compare(other) <= 0; |
| 270 | }; | |
| 271 | ||
| 272 | /** | |
| 273 | * Return whether this Long is greater than the other. | |
| 274 | * | |
| 275 | * @param {Long} other Long to compare against. | |
| 276 | * @return {Boolean} whether this Long is greater than the other. | |
| 277 | * @api public | |
| 278 | */ | |
| 279 | 1 | Long.prototype.greaterThan = function(other) { |
| 280 | 0 | return this.compare(other) > 0; |
| 281 | }; | |
| 282 | ||
| 283 | /** | |
| 284 | * Return whether this Long is greater than or equal to the other. | |
| 285 | * | |
| 286 | * @param {Long} other Long to compare against. | |
| 287 | * @return {Boolean} whether this Long is greater than or equal to the other. | |
| 288 | * @api public | |
| 289 | */ | |
| 290 | 1 | Long.prototype.greaterThanOrEqual = function(other) { |
| 291 | 0 | return this.compare(other) >= 0; |
| 292 | }; | |
| 293 | ||
| 294 | /** | |
| 295 | * Compares this Long with the given one. | |
| 296 | * | |
| 297 | * @param {Long} other Long to compare against. | |
| 298 | * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. | |
| 299 | * @api public | |
| 300 | */ | |
| 301 | 1 | Long.prototype.compare = function(other) { |
| 302 | 0 | if (this.equals(other)) { |
| 303 | 0 | return 0; |
| 304 | } | |
| 305 | ||
| 306 | 0 | var thisNeg = this.isNegative(); |
| 307 | 0 | var otherNeg = other.isNegative(); |
| 308 | 0 | if (thisNeg && !otherNeg) { |
| 309 | 0 | return -1; |
| 310 | } | |
| 311 | 0 | if (!thisNeg && otherNeg) { |
| 312 | 0 | return 1; |
| 313 | } | |
| 314 | ||
| 315 | // at this point, the signs are the same, so subtraction will not overflow | |
| 316 | 0 | if (this.subtract(other).isNegative()) { |
| 317 | 0 | return -1; |
| 318 | } else { | |
| 319 | 0 | return 1; |
| 320 | } | |
| 321 | }; | |
| 322 | ||
| 323 | /** | |
| 324 | * The negation of this value. | |
| 325 | * | |
| 326 | * @return {Long} the negation of this value. | |
| 327 | * @api public | |
| 328 | */ | |
| 329 | 1 | Long.prototype.negate = function() { |
| 330 | 1 | if (this.equals(Long.MIN_VALUE)) { |
| 331 | 0 | return Long.MIN_VALUE; |
| 332 | } else { | |
| 333 | 1 | return this.not().add(Long.ONE); |
| 334 | } | |
| 335 | }; | |
| 336 | ||
| 337 | /** | |
| 338 | * Returns the sum of this and the given Long. | |
| 339 | * | |
| 340 | * @param {Long} other Long to add to this one. | |
| 341 | * @return {Long} the sum of this and the given Long. | |
| 342 | * @api public | |
| 343 | */ | |
| 344 | 1 | Long.prototype.add = function(other) { |
| 345 | // Divide each number into 4 chunks of 16 bits, and then sum the chunks. | |
| 346 | ||
| 347 | 1 | var a48 = this.high_ >>> 16; |
| 348 | 1 | var a32 = this.high_ & 0xFFFF; |
| 349 | 1 | var a16 = this.low_ >>> 16; |
| 350 | 1 | var a00 = this.low_ & 0xFFFF; |
| 351 | ||
| 352 | 1 | var b48 = other.high_ >>> 16; |
| 353 | 1 | var b32 = other.high_ & 0xFFFF; |
| 354 | 1 | var b16 = other.low_ >>> 16; |
| 355 | 1 | var b00 = other.low_ & 0xFFFF; |
| 356 | ||
| 357 | 1 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 358 | 1 | c00 += a00 + b00; |
| 359 | 1 | c16 += c00 >>> 16; |
| 360 | 1 | c00 &= 0xFFFF; |
| 361 | 1 | c16 += a16 + b16; |
| 362 | 1 | c32 += c16 >>> 16; |
| 363 | 1 | c16 &= 0xFFFF; |
| 364 | 1 | c32 += a32 + b32; |
| 365 | 1 | c48 += c32 >>> 16; |
| 366 | 1 | c32 &= 0xFFFF; |
| 367 | 1 | c48 += a48 + b48; |
| 368 | 1 | c48 &= 0xFFFF; |
| 369 | 1 | return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 370 | }; | |
| 371 | ||
| 372 | /** | |
| 373 | * Returns the difference of this and the given Long. | |
| 374 | * | |
| 375 | * @param {Long} other Long to subtract from this. | |
| 376 | * @return {Long} the difference of this and the given Long. | |
| 377 | * @api public | |
| 378 | */ | |
| 379 | 1 | Long.prototype.subtract = function(other) { |
| 380 | 0 | return this.add(other.negate()); |
| 381 | }; | |
| 382 | ||
| 383 | /** | |
| 384 | * Returns the product of this and the given Long. | |
| 385 | * | |
| 386 | * @param {Long} other Long to multiply with this. | |
| 387 | * @return {Long} the product of this and the other. | |
| 388 | * @api public | |
| 389 | */ | |
| 390 | 1 | Long.prototype.multiply = function(other) { |
| 391 | 0 | if (this.isZero()) { |
| 392 | 0 | return Long.ZERO; |
| 393 | 0 | } else if (other.isZero()) { |
| 394 | 0 | return Long.ZERO; |
| 395 | } | |
| 396 | ||
| 397 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 398 | 0 | return other.isOdd() ? Long.MIN_VALUE : Long.ZERO; |
| 399 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 400 | 0 | return this.isOdd() ? Long.MIN_VALUE : Long.ZERO; |
| 401 | } | |
| 402 | ||
| 403 | 0 | if (this.isNegative()) { |
| 404 | 0 | if (other.isNegative()) { |
| 405 | 0 | return this.negate().multiply(other.negate()); |
| 406 | } else { | |
| 407 | 0 | return this.negate().multiply(other).negate(); |
| 408 | } | |
| 409 | 0 | } else if (other.isNegative()) { |
| 410 | 0 | return this.multiply(other.negate()).negate(); |
| 411 | } | |
| 412 | ||
| 413 | // If both Longs are small, use float multiplication | |
| 414 | 0 | if (this.lessThan(Long.TWO_PWR_24_) && |
| 415 | other.lessThan(Long.TWO_PWR_24_)) { | |
| 416 | 0 | return Long.fromNumber(this.toNumber() * other.toNumber()); |
| 417 | } | |
| 418 | ||
| 419 | // Divide each Long into 4 chunks of 16 bits, and then add up 4x4 products. | |
| 420 | // We can skip products that would overflow. | |
| 421 | ||
| 422 | 0 | var a48 = this.high_ >>> 16; |
| 423 | 0 | var a32 = this.high_ & 0xFFFF; |
| 424 | 0 | var a16 = this.low_ >>> 16; |
| 425 | 0 | var a00 = this.low_ & 0xFFFF; |
| 426 | ||
| 427 | 0 | var b48 = other.high_ >>> 16; |
| 428 | 0 | var b32 = other.high_ & 0xFFFF; |
| 429 | 0 | var b16 = other.low_ >>> 16; |
| 430 | 0 | var b00 = other.low_ & 0xFFFF; |
| 431 | ||
| 432 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 433 | 0 | c00 += a00 * b00; |
| 434 | 0 | c16 += c00 >>> 16; |
| 435 | 0 | c00 &= 0xFFFF; |
| 436 | 0 | c16 += a16 * b00; |
| 437 | 0 | c32 += c16 >>> 16; |
| 438 | 0 | c16 &= 0xFFFF; |
| 439 | 0 | c16 += a00 * b16; |
| 440 | 0 | c32 += c16 >>> 16; |
| 441 | 0 | c16 &= 0xFFFF; |
| 442 | 0 | c32 += a32 * b00; |
| 443 | 0 | c48 += c32 >>> 16; |
| 444 | 0 | c32 &= 0xFFFF; |
| 445 | 0 | c32 += a16 * b16; |
| 446 | 0 | c48 += c32 >>> 16; |
| 447 | 0 | c32 &= 0xFFFF; |
| 448 | 0 | c32 += a00 * b32; |
| 449 | 0 | c48 += c32 >>> 16; |
| 450 | 0 | c32 &= 0xFFFF; |
| 451 | 0 | c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; |
| 452 | 0 | c48 &= 0xFFFF; |
| 453 | 0 | return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 454 | }; | |
| 455 | ||
| 456 | /** | |
| 457 | * Returns this Long divided by the given one. | |
| 458 | * | |
| 459 | * @param {Long} other Long by which to divide. | |
| 460 | * @return {Long} this Long divided by the given one. | |
| 461 | * @api public | |
| 462 | */ | |
| 463 | 1 | Long.prototype.div = function(other) { |
| 464 | 0 | if (other.isZero()) { |
| 465 | 0 | throw Error('division by zero'); |
| 466 | 0 | } else if (this.isZero()) { |
| 467 | 0 | return Long.ZERO; |
| 468 | } | |
| 469 | ||
| 470 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 471 | 0 | if (other.equals(Long.ONE) || |
| 472 | other.equals(Long.NEG_ONE)) { | |
| 473 | 0 | return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE |
| 474 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 475 | 0 | return Long.ONE; |
| 476 | } else { | |
| 477 | // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. | |
| 478 | 0 | var halfThis = this.shiftRight(1); |
| 479 | 0 | var approx = halfThis.div(other).shiftLeft(1); |
| 480 | 0 | if (approx.equals(Long.ZERO)) { |
| 481 | 0 | return other.isNegative() ? Long.ONE : Long.NEG_ONE; |
| 482 | } else { | |
| 483 | 0 | var rem = this.subtract(other.multiply(approx)); |
| 484 | 0 | var result = approx.add(rem.div(other)); |
| 485 | 0 | return result; |
| 486 | } | |
| 487 | } | |
| 488 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 489 | 0 | return Long.ZERO; |
| 490 | } | |
| 491 | ||
| 492 | 0 | if (this.isNegative()) { |
| 493 | 0 | if (other.isNegative()) { |
| 494 | 0 | return this.negate().div(other.negate()); |
| 495 | } else { | |
| 496 | 0 | return this.negate().div(other).negate(); |
| 497 | } | |
| 498 | 0 | } else if (other.isNegative()) { |
| 499 | 0 | return this.div(other.negate()).negate(); |
| 500 | } | |
| 501 | ||
| 502 | // Repeat the following until the remainder is less than other: find a | |
| 503 | // floating-point that approximates remainder / other *from below*, add this | |
| 504 | // into the result, and subtract it from the remainder. It is critical that | |
| 505 | // the approximate value is less than or equal to the real value so that the | |
| 506 | // remainder never becomes negative. | |
| 507 | 0 | var res = Long.ZERO; |
| 508 | 0 | var rem = this; |
| 509 | 0 | while (rem.greaterThanOrEqual(other)) { |
| 510 | // Approximate the result of division. This may be a little greater or | |
| 511 | // smaller than the actual value. | |
| 512 | 0 | var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); |
| 513 | ||
| 514 | // We will tweak the approximate result by changing it in the 48-th digit or | |
| 515 | // the smallest non-fractional digit, whichever is larger. | |
| 516 | 0 | var log2 = Math.ceil(Math.log(approx) / Math.LN2); |
| 517 | 0 | var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); |
| 518 | ||
| 519 | // Decrease the approximation until it is smaller than the remainder. Note | |
| 520 | // that if it is too large, the product overflows and is negative. | |
| 521 | 0 | var approxRes = Long.fromNumber(approx); |
| 522 | 0 | var approxRem = approxRes.multiply(other); |
| 523 | 0 | while (approxRem.isNegative() || approxRem.greaterThan(rem)) { |
| 524 | 0 | approx -= delta; |
| 525 | 0 | approxRes = Long.fromNumber(approx); |
| 526 | 0 | approxRem = approxRes.multiply(other); |
| 527 | } | |
| 528 | ||
| 529 | // We know the answer can't be zero... and actually, zero would cause | |
| 530 | // infinite recursion since we would make no progress. | |
| 531 | 0 | if (approxRes.isZero()) { |
| 532 | 0 | approxRes = Long.ONE; |
| 533 | } | |
| 534 | ||
| 535 | 0 | res = res.add(approxRes); |
| 536 | 0 | rem = rem.subtract(approxRem); |
| 537 | } | |
| 538 | 0 | return res; |
| 539 | }; | |
| 540 | ||
| 541 | /** | |
| 542 | * Returns this Long modulo the given one. | |
| 543 | * | |
| 544 | * @param {Long} other Long by which to mod. | |
| 545 | * @return {Long} this Long modulo the given one. | |
| 546 | * @api public | |
| 547 | */ | |
| 548 | 1 | Long.prototype.modulo = function(other) { |
| 549 | 0 | return this.subtract(this.div(other).multiply(other)); |
| 550 | }; | |
| 551 | ||
| 552 | /** | |
| 553 | * The bitwise-NOT of this value. | |
| 554 | * | |
| 555 | * @return {Long} the bitwise-NOT of this value. | |
| 556 | * @api public | |
| 557 | */ | |
| 558 | 1 | Long.prototype.not = function() { |
| 559 | 1 | return Long.fromBits(~this.low_, ~this.high_); |
| 560 | }; | |
| 561 | ||
| 562 | /** | |
| 563 | * Returns the bitwise-AND of this Long and the given one. | |
| 564 | * | |
| 565 | * @param {Long} other the Long with which to AND. | |
| 566 | * @return {Long} the bitwise-AND of this and the other. | |
| 567 | * @api public | |
| 568 | */ | |
| 569 | 1 | Long.prototype.and = function(other) { |
| 570 | 0 | return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_); |
| 571 | }; | |
| 572 | ||
| 573 | /** | |
| 574 | * Returns the bitwise-OR of this Long and the given one. | |
| 575 | * | |
| 576 | * @param {Long} other the Long with which to OR. | |
| 577 | * @return {Long} the bitwise-OR of this and the other. | |
| 578 | * @api public | |
| 579 | */ | |
| 580 | 1 | Long.prototype.or = function(other) { |
| 581 | 0 | return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_); |
| 582 | }; | |
| 583 | ||
| 584 | /** | |
| 585 | * Returns the bitwise-XOR of this Long and the given one. | |
| 586 | * | |
| 587 | * @param {Long} other the Long with which to XOR. | |
| 588 | * @return {Long} the bitwise-XOR of this and the other. | |
| 589 | * @api public | |
| 590 | */ | |
| 591 | 1 | Long.prototype.xor = function(other) { |
| 592 | 0 | return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); |
| 593 | }; | |
| 594 | ||
| 595 | /** | |
| 596 | * Returns this Long with bits shifted to the left by the given amount. | |
| 597 | * | |
| 598 | * @param {Number} numBits the number of bits by which to shift. | |
| 599 | * @return {Long} this shifted to the left by the given amount. | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | 1 | Long.prototype.shiftLeft = function(numBits) { |
| 603 | 0 | numBits &= 63; |
| 604 | 0 | if (numBits == 0) { |
| 605 | 0 | return this; |
| 606 | } else { | |
| 607 | 0 | var low = this.low_; |
| 608 | 0 | if (numBits < 32) { |
| 609 | 0 | var high = this.high_; |
| 610 | 0 | return Long.fromBits( |
| 611 | low << numBits, | |
| 612 | (high << numBits) | (low >>> (32 - numBits))); | |
| 613 | } else { | |
| 614 | 0 | return Long.fromBits(0, low << (numBits - 32)); |
| 615 | } | |
| 616 | } | |
| 617 | }; | |
| 618 | ||
| 619 | /** | |
| 620 | * Returns this Long with bits shifted to the right by the given amount. | |
| 621 | * | |
| 622 | * @param {Number} numBits the number of bits by which to shift. | |
| 623 | * @return {Long} this shifted to the right by the given amount. | |
| 624 | * @api public | |
| 625 | */ | |
| 626 | 1 | Long.prototype.shiftRight = function(numBits) { |
| 627 | 0 | numBits &= 63; |
| 628 | 0 | if (numBits == 0) { |
| 629 | 0 | return this; |
| 630 | } else { | |
| 631 | 0 | var high = this.high_; |
| 632 | 0 | if (numBits < 32) { |
| 633 | 0 | var low = this.low_; |
| 634 | 0 | return Long.fromBits( |
| 635 | (low >>> numBits) | (high << (32 - numBits)), | |
| 636 | high >> numBits); | |
| 637 | } else { | |
| 638 | 0 | return Long.fromBits( |
| 639 | high >> (numBits - 32), | |
| 640 | high >= 0 ? 0 : -1); | |
| 641 | } | |
| 642 | } | |
| 643 | }; | |
| 644 | ||
| 645 | /** | |
| 646 | * Returns this Long with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. | |
| 647 | * | |
| 648 | * @param {Number} numBits the number of bits by which to shift. | |
| 649 | * @return {Long} this shifted to the right by the given amount, with zeros placed into the new leading bits. | |
| 650 | * @api public | |
| 651 | */ | |
| 652 | 1 | Long.prototype.shiftRightUnsigned = function(numBits) { |
| 653 | 0 | numBits &= 63; |
| 654 | 0 | if (numBits == 0) { |
| 655 | 0 | return this; |
| 656 | } else { | |
| 657 | 0 | var high = this.high_; |
| 658 | 0 | if (numBits < 32) { |
| 659 | 0 | var low = this.low_; |
| 660 | 0 | return Long.fromBits( |
| 661 | (low >>> numBits) | (high << (32 - numBits)), | |
| 662 | high >>> numBits); | |
| 663 | 0 | } else if (numBits == 32) { |
| 664 | 0 | return Long.fromBits(high, 0); |
| 665 | } else { | |
| 666 | 0 | return Long.fromBits(high >>> (numBits - 32), 0); |
| 667 | } | |
| 668 | } | |
| 669 | }; | |
| 670 | ||
| 671 | /** | |
| 672 | * Returns a Long representing the given (32-bit) integer value. | |
| 673 | * | |
| 674 | * @param {Number} value the 32-bit integer in question. | |
| 675 | * @return {Long} the corresponding Long value. | |
| 676 | * @api public | |
| 677 | */ | |
| 678 | 1 | Long.fromInt = function(value) { |
| 679 | 4 | if (-128 <= value && value < 128) { |
| 680 | 3 | var cachedObj = Long.INT_CACHE_[value]; |
| 681 | 3 | if (cachedObj) { |
| 682 | 0 | return cachedObj; |
| 683 | } | |
| 684 | } | |
| 685 | ||
| 686 | 4 | var obj = new Long(value | 0, value < 0 ? -1 : 0); |
| 687 | 4 | if (-128 <= value && value < 128) { |
| 688 | 3 | Long.INT_CACHE_[value] = obj; |
| 689 | } | |
| 690 | 4 | return obj; |
| 691 | }; | |
| 692 | ||
| 693 | /** | |
| 694 | * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. | |
| 695 | * | |
| 696 | * @param {Number} value the number in question. | |
| 697 | * @return {Long} the corresponding Long value. | |
| 698 | * @api public | |
| 699 | */ | |
| 700 | 1 | Long.fromNumber = function(value) { |
| 701 | 3 | if (isNaN(value) || !isFinite(value)) { |
| 702 | 0 | return Long.ZERO; |
| 703 | 3 | } else if (value <= -Long.TWO_PWR_63_DBL_) { |
| 704 | 0 | return Long.MIN_VALUE; |
| 705 | 3 | } else if (value + 1 >= Long.TWO_PWR_63_DBL_) { |
| 706 | 0 | return Long.MAX_VALUE; |
| 707 | 3 | } else if (value < 0) { |
| 708 | 1 | return Long.fromNumber(-value).negate(); |
| 709 | } else { | |
| 710 | 2 | return new Long( |
| 711 | (value % Long.TWO_PWR_32_DBL_) | 0, | |
| 712 | (value / Long.TWO_PWR_32_DBL_) | 0); | |
| 713 | } | |
| 714 | }; | |
| 715 | ||
| 716 | /** | |
| 717 | * Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. | |
| 718 | * | |
| 719 | * @param {Number} lowBits the low 32-bits. | |
| 720 | * @param {Number} highBits the high 32-bits. | |
| 721 | * @return {Long} the corresponding Long value. | |
| 722 | * @api public | |
| 723 | */ | |
| 724 | 1 | Long.fromBits = function(lowBits, highBits) { |
| 725 | 4 | return new Long(lowBits, highBits); |
| 726 | }; | |
| 727 | ||
| 728 | /** | |
| 729 | * Returns a Long representation of the given string, written using the given radix. | |
| 730 | * | |
| 731 | * @param {String} str the textual representation of the Long. | |
| 732 | * @param {Number} opt_radix the radix in which the text is written. | |
| 733 | * @return {Long} the corresponding Long value. | |
| 734 | * @api public | |
| 735 | */ | |
| 736 | 1 | Long.fromString = function(str, opt_radix) { |
| 737 | 0 | if (str.length == 0) { |
| 738 | 0 | throw Error('number format error: empty string'); |
| 739 | } | |
| 740 | ||
| 741 | 0 | var radix = opt_radix || 10; |
| 742 | 0 | if (radix < 2 || 36 < radix) { |
| 743 | 0 | throw Error('radix out of range: ' + radix); |
| 744 | } | |
| 745 | ||
| 746 | 0 | if (str.charAt(0) == '-') { |
| 747 | 0 | return Long.fromString(str.substring(1), radix).negate(); |
| 748 | 0 | } else if (str.indexOf('-') >= 0) { |
| 749 | 0 | throw Error('number format error: interior "-" character: ' + str); |
| 750 | } | |
| 751 | ||
| 752 | // Do several (8) digits each time through the loop, so as to | |
| 753 | // minimize the calls to the very expensive emulated div. | |
| 754 | 0 | var radixToPower = Long.fromNumber(Math.pow(radix, 8)); |
| 755 | ||
| 756 | 0 | var result = Long.ZERO; |
| 757 | 0 | for (var i = 0; i < str.length; i += 8) { |
| 758 | 0 | var size = Math.min(8, str.length - i); |
| 759 | 0 | var value = parseInt(str.substring(i, i + size), radix); |
| 760 | 0 | if (size < 8) { |
| 761 | 0 | var power = Long.fromNumber(Math.pow(radix, size)); |
| 762 | 0 | result = result.multiply(power).add(Long.fromNumber(value)); |
| 763 | } else { | |
| 764 | 0 | result = result.multiply(radixToPower); |
| 765 | 0 | result = result.add(Long.fromNumber(value)); |
| 766 | } | |
| 767 | } | |
| 768 | 0 | return result; |
| 769 | }; | |
| 770 | ||
| 771 | // NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the | |
| 772 | // from* methods on which they depend. | |
| 773 | ||
| 774 | ||
| 775 | /** | |
| 776 | * A cache of the Long representations of small integer values. | |
| 777 | * @type {Object} | |
| 778 | * @api private | |
| 779 | */ | |
| 780 | 1 | Long.INT_CACHE_ = {}; |
| 781 | ||
| 782 | // NOTE: the compiler should inline these constant values below and then remove | |
| 783 | // these variables, so there should be no runtime penalty for these. | |
| 784 | ||
| 785 | /** | |
| 786 | * Number used repeated below in calculations. This must appear before the | |
| 787 | * first call to any from* function below. | |
| 788 | * @type {number} | |
| 789 | * @api private | |
| 790 | */ | |
| 791 | 1 | Long.TWO_PWR_16_DBL_ = 1 << 16; |
| 792 | ||
| 793 | /** | |
| 794 | * @type {number} | |
| 795 | * @api private | |
| 796 | */ | |
| 797 | 1 | Long.TWO_PWR_24_DBL_ = 1 << 24; |
| 798 | ||
| 799 | /** | |
| 800 | * @type {number} | |
| 801 | * @api private | |
| 802 | */ | |
| 803 | 1 | Long.TWO_PWR_32_DBL_ = Long.TWO_PWR_16_DBL_ * Long.TWO_PWR_16_DBL_; |
| 804 | ||
| 805 | /** | |
| 806 | * @type {number} | |
| 807 | * @api private | |
| 808 | */ | |
| 809 | 1 | Long.TWO_PWR_31_DBL_ = Long.TWO_PWR_32_DBL_ / 2; |
| 810 | ||
| 811 | /** | |
| 812 | * @type {number} | |
| 813 | * @api private | |
| 814 | */ | |
| 815 | 1 | Long.TWO_PWR_48_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_16_DBL_; |
| 816 | ||
| 817 | /** | |
| 818 | * @type {number} | |
| 819 | * @api private | |
| 820 | */ | |
| 821 | 1 | Long.TWO_PWR_64_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_32_DBL_; |
| 822 | ||
| 823 | /** | |
| 824 | * @type {number} | |
| 825 | * @api private | |
| 826 | */ | |
| 827 | 1 | Long.TWO_PWR_63_DBL_ = Long.TWO_PWR_64_DBL_ / 2; |
| 828 | ||
| 829 | /** @type {Long} */ | |
| 830 | 1 | Long.ZERO = Long.fromInt(0); |
| 831 | ||
| 832 | /** @type {Long} */ | |
| 833 | 1 | Long.ONE = Long.fromInt(1); |
| 834 | ||
| 835 | /** @type {Long} */ | |
| 836 | 1 | Long.NEG_ONE = Long.fromInt(-1); |
| 837 | ||
| 838 | /** @type {Long} */ | |
| 839 | 1 | Long.MAX_VALUE = |
| 840 | Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); | |
| 841 | ||
| 842 | /** @type {Long} */ | |
| 843 | 1 | Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0); |
| 844 | ||
| 845 | /** | |
| 846 | * @type {Long} | |
| 847 | * @api private | |
| 848 | */ | |
| 849 | 1 | Long.TWO_PWR_24_ = Long.fromInt(1 << 24); |
| 850 | ||
| 851 | /** | |
| 852 | * Expose. | |
| 853 | */ | |
| 854 | 1 | exports.Long = Long; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON MaxKey type. | |
| 3 | * | |
| 4 | * @class Represents the BSON MaxKey type. | |
| 5 | * @return {MaxKey} | |
| 6 | */ | |
| 7 | 1 | function MaxKey() { |
| 8 | 0 | if(!(this instanceof MaxKey)) return new MaxKey(); |
| 9 | ||
| 10 | 0 | this._bsontype = 'MaxKey'; |
| 11 | } | |
| 12 | ||
| 13 | 1 | exports.MaxKey = MaxKey; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON MinKey type. | |
| 3 | * | |
| 4 | * @class Represents the BSON MinKey type. | |
| 5 | * @return {MinKey} | |
| 6 | */ | |
| 7 | 1 | function MinKey() { |
| 8 | 0 | if(!(this instanceof MinKey)) return new MinKey(); |
| 9 | ||
| 10 | 0 | this._bsontype = 'MinKey'; |
| 11 | } | |
| 12 | ||
| 13 | 1 | exports.MinKey = MinKey; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | var BinaryParser = require('./binary_parser').BinaryParser; |
| 5 | ||
| 6 | /** | |
| 7 | * Machine id. | |
| 8 | * | |
| 9 | * Create a random 3-byte value (i.e. unique for this | |
| 10 | * process). Other drivers use a md5 of the machine id here, but | |
| 11 | * that would mean an asyc call to gethostname, so we don't bother. | |
| 12 | */ | |
| 13 | 1 | var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10); |
| 14 | ||
| 15 | // Regular expression that checks for hex value | |
| 16 | 1 | var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$"); |
| 17 | ||
| 18 | /** | |
| 19 | * Create a new ObjectID instance | |
| 20 | * | |
| 21 | * @class Represents the BSON ObjectID type | |
| 22 | * @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number. | |
| 23 | * @return {Object} instance of ObjectID. | |
| 24 | */ | |
| 25 | 1 | var ObjectID = function ObjectID(id) { |
| 26 | 0 | if(!(this instanceof ObjectID)) return new ObjectID(id); |
| 27 | ||
| 28 | 0 | this._bsontype = 'ObjectID'; |
| 29 | 0 | var __id = null; |
| 30 | ||
| 31 | // Throw an error if it's not a valid setup | |
| 32 | 0 | if(id != null && 'number' != typeof id && (id.length != 12 && id.length != 24)) |
| 33 | 0 | throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); |
| 34 | ||
| 35 | // Generate id based on the input | |
| 36 | 0 | if(id == null || typeof id == 'number') { |
| 37 | // convert to 12 byte binary string | |
| 38 | 0 | this.id = this.generate(id); |
| 39 | 0 | } else if(id != null && id.length === 12) { |
| 40 | // assume 12 byte string | |
| 41 | 0 | this.id = id; |
| 42 | 0 | } else if(checkForHexRegExp.test(id)) { |
| 43 | 0 | return ObjectID.createFromHexString(id); |
| 44 | } else { | |
| 45 | 0 | throw new Error("Value passed in is not a valid 24 character hex string"); |
| 46 | } | |
| 47 | ||
| 48 | 0 | if(ObjectID.cacheHexString) this.__id = this.toHexString(); |
| 49 | }; | |
| 50 | ||
| 51 | // Allow usage of ObjectId aswell as ObjectID | |
| 52 | 1 | var ObjectId = ObjectID; |
| 53 | ||
| 54 | /** | |
| 55 | * Return the ObjectID id as a 24 byte hex string representation | |
| 56 | * | |
| 57 | * @return {String} return the 24 byte hex string representation. | |
| 58 | * @api public | |
| 59 | */ | |
| 60 | 1 | ObjectID.prototype.toHexString = function() { |
| 61 | 0 | if(ObjectID.cacheHexString && this.__id) return this.__id; |
| 62 | ||
| 63 | 0 | var hexString = '' |
| 64 | , number | |
| 65 | , value; | |
| 66 | ||
| 67 | 0 | for (var index = 0, len = this.id.length; index < len; index++) { |
| 68 | 0 | value = BinaryParser.toByte(this.id[index]); |
| 69 | 0 | number = value <= 15 |
| 70 | ? '0' + value.toString(16) | |
| 71 | : value.toString(16); | |
| 72 | 0 | hexString = hexString + number; |
| 73 | } | |
| 74 | ||
| 75 | 0 | if(ObjectID.cacheHexString) this.__id = hexString; |
| 76 | 0 | return hexString; |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Update the ObjectID index used in generating new ObjectID's on the driver | |
| 81 | * | |
| 82 | * @return {Number} returns next index value. | |
| 83 | * @api private | |
| 84 | */ | |
| 85 | 1 | ObjectID.prototype.get_inc = function() { |
| 86 | 0 | return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF; |
| 87 | }; | |
| 88 | ||
| 89 | /** | |
| 90 | * Update the ObjectID index used in generating new ObjectID's on the driver | |
| 91 | * | |
| 92 | * @return {Number} returns next index value. | |
| 93 | * @api private | |
| 94 | */ | |
| 95 | 1 | ObjectID.prototype.getInc = function() { |
| 96 | 0 | return this.get_inc(); |
| 97 | }; | |
| 98 | ||
| 99 | /** | |
| 100 | * Generate a 12 byte id string used in ObjectID's | |
| 101 | * | |
| 102 | * @param {Number} [time] optional parameter allowing to pass in a second based timestamp. | |
| 103 | * @return {String} return the 12 byte id binary string. | |
| 104 | * @api private | |
| 105 | */ | |
| 106 | 1 | ObjectID.prototype.generate = function(time) { |
| 107 | 0 | if ('number' != typeof time) { |
| 108 | 0 | time = parseInt(Date.now()/1000,10); |
| 109 | } | |
| 110 | ||
| 111 | 0 | var time4Bytes = BinaryParser.encodeInt(time, 32, true, true); |
| 112 | /* for time-based ObjectID the bytes following the time will be zeroed */ | |
| 113 | 0 | var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false); |
| 114 | 0 | var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid); |
| 115 | 0 | var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true); |
| 116 | ||
| 117 | 0 | return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes; |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Converts the id into a 24 byte hex string for printing | |
| 122 | * | |
| 123 | * @return {String} return the 24 byte hex string representation. | |
| 124 | * @api private | |
| 125 | */ | |
| 126 | 1 | ObjectID.prototype.toString = function() { |
| 127 | 0 | return this.toHexString(); |
| 128 | }; | |
| 129 | ||
| 130 | /** | |
| 131 | * Converts to a string representation of this Id. | |
| 132 | * | |
| 133 | * @return {String} return the 24 byte hex string representation. | |
| 134 | * @api private | |
| 135 | */ | |
| 136 | 1 | ObjectID.prototype.inspect = ObjectID.prototype.toString; |
| 137 | ||
| 138 | /** | |
| 139 | * Converts to its JSON representation. | |
| 140 | * | |
| 141 | * @return {String} return the 24 byte hex string representation. | |
| 142 | * @api private | |
| 143 | */ | |
| 144 | 1 | ObjectID.prototype.toJSON = function() { |
| 145 | 0 | return this.toHexString(); |
| 146 | }; | |
| 147 | ||
| 148 | /** | |
| 149 | * Compares the equality of this ObjectID with `otherID`. | |
| 150 | * | |
| 151 | * @param {Object} otherID ObjectID instance to compare against. | |
| 152 | * @return {Bool} the result of comparing two ObjectID's | |
| 153 | * @api public | |
| 154 | */ | |
| 155 | 1 | ObjectID.prototype.equals = function equals (otherID) { |
| 156 | 0 | var id = (otherID instanceof ObjectID || otherID.toHexString) |
| 157 | ? otherID.id | |
| 158 | : ObjectID.createFromHexString(otherID).id; | |
| 159 | ||
| 160 | 0 | return this.id === id; |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Returns the generation date (accurate up to the second) that this ID was generated. | |
| 165 | * | |
| 166 | * @return {Date} the generation date | |
| 167 | * @api public | |
| 168 | */ | |
| 169 | 1 | ObjectID.prototype.getTimestamp = function() { |
| 170 | 0 | var timestamp = new Date(); |
| 171 | 0 | timestamp.setTime(Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)) * 1000); |
| 172 | 0 | return timestamp; |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * @ignore | |
| 177 | * @api private | |
| 178 | */ | |
| 179 | 1 | ObjectID.index = parseInt(Math.random() * 0xFFFFFF, 10); |
| 180 | ||
| 181 | 1 | ObjectID.createPk = function createPk () { |
| 182 | 0 | return new ObjectID(); |
| 183 | }; | |
| 184 | ||
| 185 | /** | |
| 186 | * Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID. | |
| 187 | * | |
| 188 | * @param {Number} time an integer number representing a number of seconds. | |
| 189 | * @return {ObjectID} return the created ObjectID | |
| 190 | * @api public | |
| 191 | */ | |
| 192 | 1 | ObjectID.createFromTime = function createFromTime (time) { |
| 193 | 0 | var id = BinaryParser.encodeInt(time, 32, true, true) + |
| 194 | BinaryParser.encodeInt(0, 64, true, true); | |
| 195 | 0 | return new ObjectID(id); |
| 196 | }; | |
| 197 | ||
| 198 | /** | |
| 199 | * Creates an ObjectID from a hex string representation of an ObjectID. | |
| 200 | * | |
| 201 | * @param {String} hexString create a ObjectID from a passed in 24 byte hexstring. | |
| 202 | * @return {ObjectID} return the created ObjectID | |
| 203 | * @api public | |
| 204 | */ | |
| 205 | 1 | ObjectID.createFromHexString = function createFromHexString (hexString) { |
| 206 | // Throw an error if it's not a valid setup | |
| 207 | 0 | if(typeof hexString === 'undefined' || hexString != null && hexString.length != 24) |
| 208 | 0 | throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); |
| 209 | ||
| 210 | 0 | var len = hexString.length; |
| 211 | ||
| 212 | 0 | if(len > 12*2) { |
| 213 | 0 | throw new Error('Id cannot be longer than 12 bytes'); |
| 214 | } | |
| 215 | ||
| 216 | 0 | var result = '' |
| 217 | , string | |
| 218 | , number; | |
| 219 | ||
| 220 | 0 | for (var index = 0; index < len; index += 2) { |
| 221 | 0 | string = hexString.substr(index, 2); |
| 222 | 0 | number = parseInt(string, 16); |
| 223 | 0 | result += BinaryParser.fromByte(number); |
| 224 | } | |
| 225 | ||
| 226 | 0 | return new ObjectID(result, hexString); |
| 227 | }; | |
| 228 | ||
| 229 | /** | |
| 230 | * @ignore | |
| 231 | */ | |
| 232 | 1 | Object.defineProperty(ObjectID.prototype, "generationTime", { |
| 233 | enumerable: true | |
| 234 | , get: function () { | |
| 235 | 0 | return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)); |
| 236 | } | |
| 237 | , set: function (value) { | |
| 238 | 0 | var value = BinaryParser.encodeInt(value, 32, true, true); |
| 239 | 0 | this.id = value + this.id.substr(4); |
| 240 | // delete this.__id; | |
| 241 | 0 | this.toHexString(); |
| 242 | } | |
| 243 | }); | |
| 244 | ||
| 245 | /** | |
| 246 | * Expose. | |
| 247 | */ | |
| 248 | 1 | exports.ObjectID = ObjectID; |
| 249 | 1 | exports.ObjectId = ObjectID; |
| 250 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Symbol type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Symbol type. | |
| 5 | * @param {String} value the string representing the symbol. | |
| 6 | * @return {Symbol} | |
| 7 | */ | |
| 8 | 1 | function Symbol(value) { |
| 9 | 0 | if(!(this instanceof Symbol)) return new Symbol(value); |
| 10 | 0 | this._bsontype = 'Symbol'; |
| 11 | 0 | this.value = value; |
| 12 | } | |
| 13 | ||
| 14 | /** | |
| 15 | * Access the wrapped string value. | |
| 16 | * | |
| 17 | * @return {String} returns the wrapped string. | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | 1 | Symbol.prototype.valueOf = function() { |
| 21 | 0 | return this.value; |
| 22 | }; | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | * @api private | |
| 27 | */ | |
| 28 | 1 | Symbol.prototype.toString = function() { |
| 29 | 0 | return this.value; |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * @ignore | |
| 34 | * @api private | |
| 35 | */ | |
| 36 | 1 | Symbol.prototype.inspect = function() { |
| 37 | 0 | return this.value; |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * @ignore | |
| 42 | * @api private | |
| 43 | */ | |
| 44 | 1 | Symbol.prototype.toJSON = function() { |
| 45 | 0 | return this.value; |
| 46 | } | |
| 47 | ||
| 48 | 1 | exports.Symbol = Symbol; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 2 | // you may not use this file except in compliance with the License. | |
| 3 | // You may obtain a copy of the License at | |
| 4 | // | |
| 5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
| 6 | // | |
| 7 | // Unless required by applicable law or agreed to in writing, software | |
| 8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
| 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 10 | // See the License for the specific language governing permissions and | |
| 11 | // limitations under the License. | |
| 12 | // | |
| 13 | // Copyright 2009 Google Inc. All Rights Reserved | |
| 14 | ||
| 15 | /** | |
| 16 | * Defines a Timestamp class for representing a 64-bit two's-complement | |
| 17 | * integer value, which faithfully simulates the behavior of a Java "Timestamp". This | |
| 18 | * implementation is derived from TimestampLib in GWT. | |
| 19 | * | |
| 20 | * Constructs a 64-bit two's-complement integer, given its low and high 32-bit | |
| 21 | * values as *signed* integers. See the from* functions below for more | |
| 22 | * convenient ways of constructing Timestamps. | |
| 23 | * | |
| 24 | * The internal representation of a Timestamp is the two given signed, 32-bit values. | |
| 25 | * We use 32-bit pieces because these are the size of integers on which | |
| 26 | * Javascript performs bit-operations. For operations like addition and | |
| 27 | * multiplication, we split each number into 16-bit pieces, which can easily be | |
| 28 | * multiplied within Javascript's floating-point representation without overflow | |
| 29 | * or change in sign. | |
| 30 | * | |
| 31 | * In the algorithms below, we frequently reduce the negative case to the | |
| 32 | * positive case by negating the input(s) and then post-processing the result. | |
| 33 | * Note that we must ALWAYS check specially whether those values are MIN_VALUE | |
| 34 | * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as | |
| 35 | * a positive number, it overflows back into a negative). Not handling this | |
| 36 | * case would often result in infinite recursion. | |
| 37 | * | |
| 38 | * @class Represents the BSON Timestamp type. | |
| 39 | * @param {Number} low the low (signed) 32 bits of the Timestamp. | |
| 40 | * @param {Number} high the high (signed) 32 bits of the Timestamp. | |
| 41 | */ | |
| 42 | 1 | function Timestamp(low, high) { |
| 43 | 6 | if(!(this instanceof Timestamp)) return new Timestamp(low, high); |
| 44 | 6 | this._bsontype = 'Timestamp'; |
| 45 | /** | |
| 46 | * @type {number} | |
| 47 | * @api private | |
| 48 | */ | |
| 49 | 6 | this.low_ = low | 0; // force into 32 signed bits. |
| 50 | ||
| 51 | /** | |
| 52 | * @type {number} | |
| 53 | * @api private | |
| 54 | */ | |
| 55 | 6 | this.high_ = high | 0; // force into 32 signed bits. |
| 56 | }; | |
| 57 | ||
| 58 | /** | |
| 59 | * Return the int value. | |
| 60 | * | |
| 61 | * @return {Number} the value, assuming it is a 32-bit integer. | |
| 62 | * @api public | |
| 63 | */ | |
| 64 | 1 | Timestamp.prototype.toInt = function() { |
| 65 | 0 | return this.low_; |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Return the Number value. | |
| 70 | * | |
| 71 | * @return {Number} the closest floating-point representation to this value. | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | 1 | Timestamp.prototype.toNumber = function() { |
| 75 | 0 | return this.high_ * Timestamp.TWO_PWR_32_DBL_ + |
| 76 | this.getLowBitsUnsigned(); | |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Return the JSON value. | |
| 81 | * | |
| 82 | * @return {String} the JSON representation. | |
| 83 | * @api public | |
| 84 | */ | |
| 85 | 1 | Timestamp.prototype.toJSON = function() { |
| 86 | 0 | return this.toString(); |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Return the String value. | |
| 91 | * | |
| 92 | * @param {Number} [opt_radix] the radix in which the text should be written. | |
| 93 | * @return {String} the textual representation of this value. | |
| 94 | * @api public | |
| 95 | */ | |
| 96 | 1 | Timestamp.prototype.toString = function(opt_radix) { |
| 97 | 0 | var radix = opt_radix || 10; |
| 98 | 0 | if (radix < 2 || 36 < radix) { |
| 99 | 0 | throw Error('radix out of range: ' + radix); |
| 100 | } | |
| 101 | ||
| 102 | 0 | if (this.isZero()) { |
| 103 | 0 | return '0'; |
| 104 | } | |
| 105 | ||
| 106 | 0 | if (this.isNegative()) { |
| 107 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 108 | // We need to change the Timestamp value before it can be negated, so we remove | |
| 109 | // the bottom-most digit in this base and then recurse to do the rest. | |
| 110 | 0 | var radixTimestamp = Timestamp.fromNumber(radix); |
| 111 | 0 | var div = this.div(radixTimestamp); |
| 112 | 0 | var rem = div.multiply(radixTimestamp).subtract(this); |
| 113 | 0 | return div.toString(radix) + rem.toInt().toString(radix); |
| 114 | } else { | |
| 115 | 0 | return '-' + this.negate().toString(radix); |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | // Do several (6) digits each time through the loop, so as to | |
| 120 | // minimize the calls to the very expensive emulated div. | |
| 121 | 0 | var radixToPower = Timestamp.fromNumber(Math.pow(radix, 6)); |
| 122 | ||
| 123 | 0 | var rem = this; |
| 124 | 0 | var result = ''; |
| 125 | 0 | while (true) { |
| 126 | 0 | var remDiv = rem.div(radixToPower); |
| 127 | 0 | var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); |
| 128 | 0 | var digits = intval.toString(radix); |
| 129 | ||
| 130 | 0 | rem = remDiv; |
| 131 | 0 | if (rem.isZero()) { |
| 132 | 0 | return digits + result; |
| 133 | } else { | |
| 134 | 0 | while (digits.length < 6) { |
| 135 | 0 | digits = '0' + digits; |
| 136 | } | |
| 137 | 0 | result = '' + digits + result; |
| 138 | } | |
| 139 | } | |
| 140 | }; | |
| 141 | ||
| 142 | /** | |
| 143 | * Return the high 32-bits value. | |
| 144 | * | |
| 145 | * @return {Number} the high 32-bits as a signed value. | |
| 146 | * @api public | |
| 147 | */ | |
| 148 | 1 | Timestamp.prototype.getHighBits = function() { |
| 149 | 0 | return this.high_; |
| 150 | }; | |
| 151 | ||
| 152 | /** | |
| 153 | * Return the low 32-bits value. | |
| 154 | * | |
| 155 | * @return {Number} the low 32-bits as a signed value. | |
| 156 | * @api public | |
| 157 | */ | |
| 158 | 1 | Timestamp.prototype.getLowBits = function() { |
| 159 | 0 | return this.low_; |
| 160 | }; | |
| 161 | ||
| 162 | /** | |
| 163 | * Return the low unsigned 32-bits value. | |
| 164 | * | |
| 165 | * @return {Number} the low 32-bits as an unsigned value. | |
| 166 | * @api public | |
| 167 | */ | |
| 168 | 1 | Timestamp.prototype.getLowBitsUnsigned = function() { |
| 169 | 0 | return (this.low_ >= 0) ? |
| 170 | this.low_ : Timestamp.TWO_PWR_32_DBL_ + this.low_; | |
| 171 | }; | |
| 172 | ||
| 173 | /** | |
| 174 | * Returns the number of bits needed to represent the absolute value of this Timestamp. | |
| 175 | * | |
| 176 | * @return {Number} Returns the number of bits needed to represent the absolute value of this Timestamp. | |
| 177 | * @api public | |
| 178 | */ | |
| 179 | 1 | Timestamp.prototype.getNumBitsAbs = function() { |
| 180 | 0 | if (this.isNegative()) { |
| 181 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 182 | 0 | return 64; |
| 183 | } else { | |
| 184 | 0 | return this.negate().getNumBitsAbs(); |
| 185 | } | |
| 186 | } else { | |
| 187 | 0 | var val = this.high_ != 0 ? this.high_ : this.low_; |
| 188 | 0 | for (var bit = 31; bit > 0; bit--) { |
| 189 | 0 | if ((val & (1 << bit)) != 0) { |
| 190 | 0 | break; |
| 191 | } | |
| 192 | } | |
| 193 | 0 | return this.high_ != 0 ? bit + 33 : bit + 1; |
| 194 | } | |
| 195 | }; | |
| 196 | ||
| 197 | /** | |
| 198 | * Return whether this value is zero. | |
| 199 | * | |
| 200 | * @return {Boolean} whether this value is zero. | |
| 201 | * @api public | |
| 202 | */ | |
| 203 | 1 | Timestamp.prototype.isZero = function() { |
| 204 | 0 | return this.high_ == 0 && this.low_ == 0; |
| 205 | }; | |
| 206 | ||
| 207 | /** | |
| 208 | * Return whether this value is negative. | |
| 209 | * | |
| 210 | * @return {Boolean} whether this value is negative. | |
| 211 | * @api public | |
| 212 | */ | |
| 213 | 1 | Timestamp.prototype.isNegative = function() { |
| 214 | 0 | return this.high_ < 0; |
| 215 | }; | |
| 216 | ||
| 217 | /** | |
| 218 | * Return whether this value is odd. | |
| 219 | * | |
| 220 | * @return {Boolean} whether this value is odd. | |
| 221 | * @api public | |
| 222 | */ | |
| 223 | 1 | Timestamp.prototype.isOdd = function() { |
| 224 | 0 | return (this.low_ & 1) == 1; |
| 225 | }; | |
| 226 | ||
| 227 | /** | |
| 228 | * Return whether this Timestamp equals the other | |
| 229 | * | |
| 230 | * @param {Timestamp} other Timestamp to compare against. | |
| 231 | * @return {Boolean} whether this Timestamp equals the other | |
| 232 | * @api public | |
| 233 | */ | |
| 234 | 1 | Timestamp.prototype.equals = function(other) { |
| 235 | 0 | return (this.high_ == other.high_) && (this.low_ == other.low_); |
| 236 | }; | |
| 237 | ||
| 238 | /** | |
| 239 | * Return whether this Timestamp does not equal the other. | |
| 240 | * | |
| 241 | * @param {Timestamp} other Timestamp to compare against. | |
| 242 | * @return {Boolean} whether this Timestamp does not equal the other. | |
| 243 | * @api public | |
| 244 | */ | |
| 245 | 1 | Timestamp.prototype.notEquals = function(other) { |
| 246 | 0 | return (this.high_ != other.high_) || (this.low_ != other.low_); |
| 247 | }; | |
| 248 | ||
| 249 | /** | |
| 250 | * Return whether this Timestamp is less than the other. | |
| 251 | * | |
| 252 | * @param {Timestamp} other Timestamp to compare against. | |
| 253 | * @return {Boolean} whether this Timestamp is less than the other. | |
| 254 | * @api public | |
| 255 | */ | |
| 256 | 1 | Timestamp.prototype.lessThan = function(other) { |
| 257 | 0 | return this.compare(other) < 0; |
| 258 | }; | |
| 259 | ||
| 260 | /** | |
| 261 | * Return whether this Timestamp is less than or equal to the other. | |
| 262 | * | |
| 263 | * @param {Timestamp} other Timestamp to compare against. | |
| 264 | * @return {Boolean} whether this Timestamp is less than or equal to the other. | |
| 265 | * @api public | |
| 266 | */ | |
| 267 | 1 | Timestamp.prototype.lessThanOrEqual = function(other) { |
| 268 | 0 | return this.compare(other) <= 0; |
| 269 | }; | |
| 270 | ||
| 271 | /** | |
| 272 | * Return whether this Timestamp is greater than the other. | |
| 273 | * | |
| 274 | * @param {Timestamp} other Timestamp to compare against. | |
| 275 | * @return {Boolean} whether this Timestamp is greater than the other. | |
| 276 | * @api public | |
| 277 | */ | |
| 278 | 1 | Timestamp.prototype.greaterThan = function(other) { |
| 279 | 0 | return this.compare(other) > 0; |
| 280 | }; | |
| 281 | ||
| 282 | /** | |
| 283 | * Return whether this Timestamp is greater than or equal to the other. | |
| 284 | * | |
| 285 | * @param {Timestamp} other Timestamp to compare against. | |
| 286 | * @return {Boolean} whether this Timestamp is greater than or equal to the other. | |
| 287 | * @api public | |
| 288 | */ | |
| 289 | 1 | Timestamp.prototype.greaterThanOrEqual = function(other) { |
| 290 | 0 | return this.compare(other) >= 0; |
| 291 | }; | |
| 292 | ||
| 293 | /** | |
| 294 | * Compares this Timestamp with the given one. | |
| 295 | * | |
| 296 | * @param {Timestamp} other Timestamp to compare against. | |
| 297 | * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. | |
| 298 | * @api public | |
| 299 | */ | |
| 300 | 1 | Timestamp.prototype.compare = function(other) { |
| 301 | 0 | if (this.equals(other)) { |
| 302 | 0 | return 0; |
| 303 | } | |
| 304 | ||
| 305 | 0 | var thisNeg = this.isNegative(); |
| 306 | 0 | var otherNeg = other.isNegative(); |
| 307 | 0 | if (thisNeg && !otherNeg) { |
| 308 | 0 | return -1; |
| 309 | } | |
| 310 | 0 | if (!thisNeg && otherNeg) { |
| 311 | 0 | return 1; |
| 312 | } | |
| 313 | ||
| 314 | // at this point, the signs are the same, so subtraction will not overflow | |
| 315 | 0 | if (this.subtract(other).isNegative()) { |
| 316 | 0 | return -1; |
| 317 | } else { | |
| 318 | 0 | return 1; |
| 319 | } | |
| 320 | }; | |
| 321 | ||
| 322 | /** | |
| 323 | * The negation of this value. | |
| 324 | * | |
| 325 | * @return {Timestamp} the negation of this value. | |
| 326 | * @api public | |
| 327 | */ | |
| 328 | 1 | Timestamp.prototype.negate = function() { |
| 329 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 330 | 0 | return Timestamp.MIN_VALUE; |
| 331 | } else { | |
| 332 | 0 | return this.not().add(Timestamp.ONE); |
| 333 | } | |
| 334 | }; | |
| 335 | ||
| 336 | /** | |
| 337 | * Returns the sum of this and the given Timestamp. | |
| 338 | * | |
| 339 | * @param {Timestamp} other Timestamp to add to this one. | |
| 340 | * @return {Timestamp} the sum of this and the given Timestamp. | |
| 341 | * @api public | |
| 342 | */ | |
| 343 | 1 | Timestamp.prototype.add = function(other) { |
| 344 | // Divide each number into 4 chunks of 16 bits, and then sum the chunks. | |
| 345 | ||
| 346 | 0 | var a48 = this.high_ >>> 16; |
| 347 | 0 | var a32 = this.high_ & 0xFFFF; |
| 348 | 0 | var a16 = this.low_ >>> 16; |
| 349 | 0 | var a00 = this.low_ & 0xFFFF; |
| 350 | ||
| 351 | 0 | var b48 = other.high_ >>> 16; |
| 352 | 0 | var b32 = other.high_ & 0xFFFF; |
| 353 | 0 | var b16 = other.low_ >>> 16; |
| 354 | 0 | var b00 = other.low_ & 0xFFFF; |
| 355 | ||
| 356 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 357 | 0 | c00 += a00 + b00; |
| 358 | 0 | c16 += c00 >>> 16; |
| 359 | 0 | c00 &= 0xFFFF; |
| 360 | 0 | c16 += a16 + b16; |
| 361 | 0 | c32 += c16 >>> 16; |
| 362 | 0 | c16 &= 0xFFFF; |
| 363 | 0 | c32 += a32 + b32; |
| 364 | 0 | c48 += c32 >>> 16; |
| 365 | 0 | c32 &= 0xFFFF; |
| 366 | 0 | c48 += a48 + b48; |
| 367 | 0 | c48 &= 0xFFFF; |
| 368 | 0 | return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 369 | }; | |
| 370 | ||
| 371 | /** | |
| 372 | * Returns the difference of this and the given Timestamp. | |
| 373 | * | |
| 374 | * @param {Timestamp} other Timestamp to subtract from this. | |
| 375 | * @return {Timestamp} the difference of this and the given Timestamp. | |
| 376 | * @api public | |
| 377 | */ | |
| 378 | 1 | Timestamp.prototype.subtract = function(other) { |
| 379 | 0 | return this.add(other.negate()); |
| 380 | }; | |
| 381 | ||
| 382 | /** | |
| 383 | * Returns the product of this and the given Timestamp. | |
| 384 | * | |
| 385 | * @param {Timestamp} other Timestamp to multiply with this. | |
| 386 | * @return {Timestamp} the product of this and the other. | |
| 387 | * @api public | |
| 388 | */ | |
| 389 | 1 | Timestamp.prototype.multiply = function(other) { |
| 390 | 0 | if (this.isZero()) { |
| 391 | 0 | return Timestamp.ZERO; |
| 392 | 0 | } else if (other.isZero()) { |
| 393 | 0 | return Timestamp.ZERO; |
| 394 | } | |
| 395 | ||
| 396 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 397 | 0 | return other.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; |
| 398 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 399 | 0 | return this.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; |
| 400 | } | |
| 401 | ||
| 402 | 0 | if (this.isNegative()) { |
| 403 | 0 | if (other.isNegative()) { |
| 404 | 0 | return this.negate().multiply(other.negate()); |
| 405 | } else { | |
| 406 | 0 | return this.negate().multiply(other).negate(); |
| 407 | } | |
| 408 | 0 | } else if (other.isNegative()) { |
| 409 | 0 | return this.multiply(other.negate()).negate(); |
| 410 | } | |
| 411 | ||
| 412 | // If both Timestamps are small, use float multiplication | |
| 413 | 0 | if (this.lessThan(Timestamp.TWO_PWR_24_) && |
| 414 | other.lessThan(Timestamp.TWO_PWR_24_)) { | |
| 415 | 0 | return Timestamp.fromNumber(this.toNumber() * other.toNumber()); |
| 416 | } | |
| 417 | ||
| 418 | // Divide each Timestamp into 4 chunks of 16 bits, and then add up 4x4 products. | |
| 419 | // We can skip products that would overflow. | |
| 420 | ||
| 421 | 0 | var a48 = this.high_ >>> 16; |
| 422 | 0 | var a32 = this.high_ & 0xFFFF; |
| 423 | 0 | var a16 = this.low_ >>> 16; |
| 424 | 0 | var a00 = this.low_ & 0xFFFF; |
| 425 | ||
| 426 | 0 | var b48 = other.high_ >>> 16; |
| 427 | 0 | var b32 = other.high_ & 0xFFFF; |
| 428 | 0 | var b16 = other.low_ >>> 16; |
| 429 | 0 | var b00 = other.low_ & 0xFFFF; |
| 430 | ||
| 431 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 432 | 0 | c00 += a00 * b00; |
| 433 | 0 | c16 += c00 >>> 16; |
| 434 | 0 | c00 &= 0xFFFF; |
| 435 | 0 | c16 += a16 * b00; |
| 436 | 0 | c32 += c16 >>> 16; |
| 437 | 0 | c16 &= 0xFFFF; |
| 438 | 0 | c16 += a00 * b16; |
| 439 | 0 | c32 += c16 >>> 16; |
| 440 | 0 | c16 &= 0xFFFF; |
| 441 | 0 | c32 += a32 * b00; |
| 442 | 0 | c48 += c32 >>> 16; |
| 443 | 0 | c32 &= 0xFFFF; |
| 444 | 0 | c32 += a16 * b16; |
| 445 | 0 | c48 += c32 >>> 16; |
| 446 | 0 | c32 &= 0xFFFF; |
| 447 | 0 | c32 += a00 * b32; |
| 448 | 0 | c48 += c32 >>> 16; |
| 449 | 0 | c32 &= 0xFFFF; |
| 450 | 0 | c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; |
| 451 | 0 | c48 &= 0xFFFF; |
| 452 | 0 | return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 453 | }; | |
| 454 | ||
| 455 | /** | |
| 456 | * Returns this Timestamp divided by the given one. | |
| 457 | * | |
| 458 | * @param {Timestamp} other Timestamp by which to divide. | |
| 459 | * @return {Timestamp} this Timestamp divided by the given one. | |
| 460 | * @api public | |
| 461 | */ | |
| 462 | 1 | Timestamp.prototype.div = function(other) { |
| 463 | 0 | if (other.isZero()) { |
| 464 | 0 | throw Error('division by zero'); |
| 465 | 0 | } else if (this.isZero()) { |
| 466 | 0 | return Timestamp.ZERO; |
| 467 | } | |
| 468 | ||
| 469 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 470 | 0 | if (other.equals(Timestamp.ONE) || |
| 471 | other.equals(Timestamp.NEG_ONE)) { | |
| 472 | 0 | return Timestamp.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE |
| 473 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 474 | 0 | return Timestamp.ONE; |
| 475 | } else { | |
| 476 | // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. | |
| 477 | 0 | var halfThis = this.shiftRight(1); |
| 478 | 0 | var approx = halfThis.div(other).shiftLeft(1); |
| 479 | 0 | if (approx.equals(Timestamp.ZERO)) { |
| 480 | 0 | return other.isNegative() ? Timestamp.ONE : Timestamp.NEG_ONE; |
| 481 | } else { | |
| 482 | 0 | var rem = this.subtract(other.multiply(approx)); |
| 483 | 0 | var result = approx.add(rem.div(other)); |
| 484 | 0 | return result; |
| 485 | } | |
| 486 | } | |
| 487 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 488 | 0 | return Timestamp.ZERO; |
| 489 | } | |
| 490 | ||
| 491 | 0 | if (this.isNegative()) { |
| 492 | 0 | if (other.isNegative()) { |
| 493 | 0 | return this.negate().div(other.negate()); |
| 494 | } else { | |
| 495 | 0 | return this.negate().div(other).negate(); |
| 496 | } | |
| 497 | 0 | } else if (other.isNegative()) { |
| 498 | 0 | return this.div(other.negate()).negate(); |
| 499 | } | |
| 500 | ||
| 501 | // Repeat the following until the remainder is less than other: find a | |
| 502 | // floating-point that approximates remainder / other *from below*, add this | |
| 503 | // into the result, and subtract it from the remainder. It is critical that | |
| 504 | // the approximate value is less than or equal to the real value so that the | |
| 505 | // remainder never becomes negative. | |
| 506 | 0 | var res = Timestamp.ZERO; |
| 507 | 0 | var rem = this; |
| 508 | 0 | while (rem.greaterThanOrEqual(other)) { |
| 509 | // Approximate the result of division. This may be a little greater or | |
| 510 | // smaller than the actual value. | |
| 511 | 0 | var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); |
| 512 | ||
| 513 | // We will tweak the approximate result by changing it in the 48-th digit or | |
| 514 | // the smallest non-fractional digit, whichever is larger. | |
| 515 | 0 | var log2 = Math.ceil(Math.log(approx) / Math.LN2); |
| 516 | 0 | var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); |
| 517 | ||
| 518 | // Decrease the approximation until it is smaller than the remainder. Note | |
| 519 | // that if it is too large, the product overflows and is negative. | |
| 520 | 0 | var approxRes = Timestamp.fromNumber(approx); |
| 521 | 0 | var approxRem = approxRes.multiply(other); |
| 522 | 0 | while (approxRem.isNegative() || approxRem.greaterThan(rem)) { |
| 523 | 0 | approx -= delta; |
| 524 | 0 | approxRes = Timestamp.fromNumber(approx); |
| 525 | 0 | approxRem = approxRes.multiply(other); |
| 526 | } | |
| 527 | ||
| 528 | // We know the answer can't be zero... and actually, zero would cause | |
| 529 | // infinite recursion since we would make no progress. | |
| 530 | 0 | if (approxRes.isZero()) { |
| 531 | 0 | approxRes = Timestamp.ONE; |
| 532 | } | |
| 533 | ||
| 534 | 0 | res = res.add(approxRes); |
| 535 | 0 | rem = rem.subtract(approxRem); |
| 536 | } | |
| 537 | 0 | return res; |
| 538 | }; | |
| 539 | ||
| 540 | /** | |
| 541 | * Returns this Timestamp modulo the given one. | |
| 542 | * | |
| 543 | * @param {Timestamp} other Timestamp by which to mod. | |
| 544 | * @return {Timestamp} this Timestamp modulo the given one. | |
| 545 | * @api public | |
| 546 | */ | |
| 547 | 1 | Timestamp.prototype.modulo = function(other) { |
| 548 | 0 | return this.subtract(this.div(other).multiply(other)); |
| 549 | }; | |
| 550 | ||
| 551 | /** | |
| 552 | * The bitwise-NOT of this value. | |
| 553 | * | |
| 554 | * @return {Timestamp} the bitwise-NOT of this value. | |
| 555 | * @api public | |
| 556 | */ | |
| 557 | 1 | Timestamp.prototype.not = function() { |
| 558 | 0 | return Timestamp.fromBits(~this.low_, ~this.high_); |
| 559 | }; | |
| 560 | ||
| 561 | /** | |
| 562 | * Returns the bitwise-AND of this Timestamp and the given one. | |
| 563 | * | |
| 564 | * @param {Timestamp} other the Timestamp with which to AND. | |
| 565 | * @return {Timestamp} the bitwise-AND of this and the other. | |
| 566 | * @api public | |
| 567 | */ | |
| 568 | 1 | Timestamp.prototype.and = function(other) { |
| 569 | 0 | return Timestamp.fromBits(this.low_ & other.low_, this.high_ & other.high_); |
| 570 | }; | |
| 571 | ||
| 572 | /** | |
| 573 | * Returns the bitwise-OR of this Timestamp and the given one. | |
| 574 | * | |
| 575 | * @param {Timestamp} other the Timestamp with which to OR. | |
| 576 | * @return {Timestamp} the bitwise-OR of this and the other. | |
| 577 | * @api public | |
| 578 | */ | |
| 579 | 1 | Timestamp.prototype.or = function(other) { |
| 580 | 0 | return Timestamp.fromBits(this.low_ | other.low_, this.high_ | other.high_); |
| 581 | }; | |
| 582 | ||
| 583 | /** | |
| 584 | * Returns the bitwise-XOR of this Timestamp and the given one. | |
| 585 | * | |
| 586 | * @param {Timestamp} other the Timestamp with which to XOR. | |
| 587 | * @return {Timestamp} the bitwise-XOR of this and the other. | |
| 588 | * @api public | |
| 589 | */ | |
| 590 | 1 | Timestamp.prototype.xor = function(other) { |
| 591 | 0 | return Timestamp.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); |
| 592 | }; | |
| 593 | ||
| 594 | /** | |
| 595 | * Returns this Timestamp with bits shifted to the left by the given amount. | |
| 596 | * | |
| 597 | * @param {Number} numBits the number of bits by which to shift. | |
| 598 | * @return {Timestamp} this shifted to the left by the given amount. | |
| 599 | * @api public | |
| 600 | */ | |
| 601 | 1 | Timestamp.prototype.shiftLeft = function(numBits) { |
| 602 | 0 | numBits &= 63; |
| 603 | 0 | if (numBits == 0) { |
| 604 | 0 | return this; |
| 605 | } else { | |
| 606 | 0 | var low = this.low_; |
| 607 | 0 | if (numBits < 32) { |
| 608 | 0 | var high = this.high_; |
| 609 | 0 | return Timestamp.fromBits( |
| 610 | low << numBits, | |
| 611 | (high << numBits) | (low >>> (32 - numBits))); | |
| 612 | } else { | |
| 613 | 0 | return Timestamp.fromBits(0, low << (numBits - 32)); |
| 614 | } | |
| 615 | } | |
| 616 | }; | |
| 617 | ||
| 618 | /** | |
| 619 | * Returns this Timestamp with bits shifted to the right by the given amount. | |
| 620 | * | |
| 621 | * @param {Number} numBits the number of bits by which to shift. | |
| 622 | * @return {Timestamp} this shifted to the right by the given amount. | |
| 623 | * @api public | |
| 624 | */ | |
| 625 | 1 | Timestamp.prototype.shiftRight = function(numBits) { |
| 626 | 0 | numBits &= 63; |
| 627 | 0 | if (numBits == 0) { |
| 628 | 0 | return this; |
| 629 | } else { | |
| 630 | 0 | var high = this.high_; |
| 631 | 0 | if (numBits < 32) { |
| 632 | 0 | var low = this.low_; |
| 633 | 0 | return Timestamp.fromBits( |
| 634 | (low >>> numBits) | (high << (32 - numBits)), | |
| 635 | high >> numBits); | |
| 636 | } else { | |
| 637 | 0 | return Timestamp.fromBits( |
| 638 | high >> (numBits - 32), | |
| 639 | high >= 0 ? 0 : -1); | |
| 640 | } | |
| 641 | } | |
| 642 | }; | |
| 643 | ||
| 644 | /** | |
| 645 | * Returns this Timestamp with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. | |
| 646 | * | |
| 647 | * @param {Number} numBits the number of bits by which to shift. | |
| 648 | * @return {Timestamp} this shifted to the right by the given amount, with zeros placed into the new leading bits. | |
| 649 | * @api public | |
| 650 | */ | |
| 651 | 1 | Timestamp.prototype.shiftRightUnsigned = function(numBits) { |
| 652 | 0 | numBits &= 63; |
| 653 | 0 | if (numBits == 0) { |
| 654 | 0 | return this; |
| 655 | } else { | |
| 656 | 0 | var high = this.high_; |
| 657 | 0 | if (numBits < 32) { |
| 658 | 0 | var low = this.low_; |
| 659 | 0 | return Timestamp.fromBits( |
| 660 | (low >>> numBits) | (high << (32 - numBits)), | |
| 661 | high >>> numBits); | |
| 662 | 0 | } else if (numBits == 32) { |
| 663 | 0 | return Timestamp.fromBits(high, 0); |
| 664 | } else { | |
| 665 | 0 | return Timestamp.fromBits(high >>> (numBits - 32), 0); |
| 666 | } | |
| 667 | } | |
| 668 | }; | |
| 669 | ||
| 670 | /** | |
| 671 | * Returns a Timestamp representing the given (32-bit) integer value. | |
| 672 | * | |
| 673 | * @param {Number} value the 32-bit integer in question. | |
| 674 | * @return {Timestamp} the corresponding Timestamp value. | |
| 675 | * @api public | |
| 676 | */ | |
| 677 | 1 | Timestamp.fromInt = function(value) { |
| 678 | 4 | if (-128 <= value && value < 128) { |
| 679 | 3 | var cachedObj = Timestamp.INT_CACHE_[value]; |
| 680 | 3 | if (cachedObj) { |
| 681 | 0 | return cachedObj; |
| 682 | } | |
| 683 | } | |
| 684 | ||
| 685 | 4 | var obj = new Timestamp(value | 0, value < 0 ? -1 : 0); |
| 686 | 4 | if (-128 <= value && value < 128) { |
| 687 | 3 | Timestamp.INT_CACHE_[value] = obj; |
| 688 | } | |
| 689 | 4 | return obj; |
| 690 | }; | |
| 691 | ||
| 692 | /** | |
| 693 | * Returns a Timestamp representing the given value, provided that it is a finite number. Otherwise, zero is returned. | |
| 694 | * | |
| 695 | * @param {Number} value the number in question. | |
| 696 | * @return {Timestamp} the corresponding Timestamp value. | |
| 697 | * @api public | |
| 698 | */ | |
| 699 | 1 | Timestamp.fromNumber = function(value) { |
| 700 | 0 | if (isNaN(value) || !isFinite(value)) { |
| 701 | 0 | return Timestamp.ZERO; |
| 702 | 0 | } else if (value <= -Timestamp.TWO_PWR_63_DBL_) { |
| 703 | 0 | return Timestamp.MIN_VALUE; |
| 704 | 0 | } else if (value + 1 >= Timestamp.TWO_PWR_63_DBL_) { |
| 705 | 0 | return Timestamp.MAX_VALUE; |
| 706 | 0 | } else if (value < 0) { |
| 707 | 0 | return Timestamp.fromNumber(-value).negate(); |
| 708 | } else { | |
| 709 | 0 | return new Timestamp( |
| 710 | (value % Timestamp.TWO_PWR_32_DBL_) | 0, | |
| 711 | (value / Timestamp.TWO_PWR_32_DBL_) | 0); | |
| 712 | } | |
| 713 | }; | |
| 714 | ||
| 715 | /** | |
| 716 | * Returns a Timestamp representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. | |
| 717 | * | |
| 718 | * @param {Number} lowBits the low 32-bits. | |
| 719 | * @param {Number} highBits the high 32-bits. | |
| 720 | * @return {Timestamp} the corresponding Timestamp value. | |
| 721 | * @api public | |
| 722 | */ | |
| 723 | 1 | Timestamp.fromBits = function(lowBits, highBits) { |
| 724 | 2 | return new Timestamp(lowBits, highBits); |
| 725 | }; | |
| 726 | ||
| 727 | /** | |
| 728 | * Returns a Timestamp representation of the given string, written using the given radix. | |
| 729 | * | |
| 730 | * @param {String} str the textual representation of the Timestamp. | |
| 731 | * @param {Number} opt_radix the radix in which the text is written. | |
| 732 | * @return {Timestamp} the corresponding Timestamp value. | |
| 733 | * @api public | |
| 734 | */ | |
| 735 | 1 | Timestamp.fromString = function(str, opt_radix) { |
| 736 | 0 | if (str.length == 0) { |
| 737 | 0 | throw Error('number format error: empty string'); |
| 738 | } | |
| 739 | ||
| 740 | 0 | var radix = opt_radix || 10; |
| 741 | 0 | if (radix < 2 || 36 < radix) { |
| 742 | 0 | throw Error('radix out of range: ' + radix); |
| 743 | } | |
| 744 | ||
| 745 | 0 | if (str.charAt(0) == '-') { |
| 746 | 0 | return Timestamp.fromString(str.substring(1), radix).negate(); |
| 747 | 0 | } else if (str.indexOf('-') >= 0) { |
| 748 | 0 | throw Error('number format error: interior "-" character: ' + str); |
| 749 | } | |
| 750 | ||
| 751 | // Do several (8) digits each time through the loop, so as to | |
| 752 | // minimize the calls to the very expensive emulated div. | |
| 753 | 0 | var radixToPower = Timestamp.fromNumber(Math.pow(radix, 8)); |
| 754 | ||
| 755 | 0 | var result = Timestamp.ZERO; |
| 756 | 0 | for (var i = 0; i < str.length; i += 8) { |
| 757 | 0 | var size = Math.min(8, str.length - i); |
| 758 | 0 | var value = parseInt(str.substring(i, i + size), radix); |
| 759 | 0 | if (size < 8) { |
| 760 | 0 | var power = Timestamp.fromNumber(Math.pow(radix, size)); |
| 761 | 0 | result = result.multiply(power).add(Timestamp.fromNumber(value)); |
| 762 | } else { | |
| 763 | 0 | result = result.multiply(radixToPower); |
| 764 | 0 | result = result.add(Timestamp.fromNumber(value)); |
| 765 | } | |
| 766 | } | |
| 767 | 0 | return result; |
| 768 | }; | |
| 769 | ||
| 770 | // NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the | |
| 771 | // from* methods on which they depend. | |
| 772 | ||
| 773 | ||
| 774 | /** | |
| 775 | * A cache of the Timestamp representations of small integer values. | |
| 776 | * @type {Object} | |
| 777 | * @api private | |
| 778 | */ | |
| 779 | 1 | Timestamp.INT_CACHE_ = {}; |
| 780 | ||
| 781 | // NOTE: the compiler should inline these constant values below and then remove | |
| 782 | // these variables, so there should be no runtime penalty for these. | |
| 783 | ||
| 784 | /** | |
| 785 | * Number used repeated below in calculations. This must appear before the | |
| 786 | * first call to any from* function below. | |
| 787 | * @type {number} | |
| 788 | * @api private | |
| 789 | */ | |
| 790 | 1 | Timestamp.TWO_PWR_16_DBL_ = 1 << 16; |
| 791 | ||
| 792 | /** | |
| 793 | * @type {number} | |
| 794 | * @api private | |
| 795 | */ | |
| 796 | 1 | Timestamp.TWO_PWR_24_DBL_ = 1 << 24; |
| 797 | ||
| 798 | /** | |
| 799 | * @type {number} | |
| 800 | * @api private | |
| 801 | */ | |
| 802 | 1 | Timestamp.TWO_PWR_32_DBL_ = Timestamp.TWO_PWR_16_DBL_ * Timestamp.TWO_PWR_16_DBL_; |
| 803 | ||
| 804 | /** | |
| 805 | * @type {number} | |
| 806 | * @api private | |
| 807 | */ | |
| 808 | 1 | Timestamp.TWO_PWR_31_DBL_ = Timestamp.TWO_PWR_32_DBL_ / 2; |
| 809 | ||
| 810 | /** | |
| 811 | * @type {number} | |
| 812 | * @api private | |
| 813 | */ | |
| 814 | 1 | Timestamp.TWO_PWR_48_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_16_DBL_; |
| 815 | ||
| 816 | /** | |
| 817 | * @type {number} | |
| 818 | * @api private | |
| 819 | */ | |
| 820 | 1 | Timestamp.TWO_PWR_64_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_32_DBL_; |
| 821 | ||
| 822 | /** | |
| 823 | * @type {number} | |
| 824 | * @api private | |
| 825 | */ | |
| 826 | 1 | Timestamp.TWO_PWR_63_DBL_ = Timestamp.TWO_PWR_64_DBL_ / 2; |
| 827 | ||
| 828 | /** @type {Timestamp} */ | |
| 829 | 1 | Timestamp.ZERO = Timestamp.fromInt(0); |
| 830 | ||
| 831 | /** @type {Timestamp} */ | |
| 832 | 1 | Timestamp.ONE = Timestamp.fromInt(1); |
| 833 | ||
| 834 | /** @type {Timestamp} */ | |
| 835 | 1 | Timestamp.NEG_ONE = Timestamp.fromInt(-1); |
| 836 | ||
| 837 | /** @type {Timestamp} */ | |
| 838 | 1 | Timestamp.MAX_VALUE = |
| 839 | Timestamp.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); | |
| 840 | ||
| 841 | /** @type {Timestamp} */ | |
| 842 | 1 | Timestamp.MIN_VALUE = Timestamp.fromBits(0, 0x80000000 | 0); |
| 843 | ||
| 844 | /** | |
| 845 | * @type {Timestamp} | |
| 846 | * @api private | |
| 847 | */ | |
| 848 | 1 | Timestamp.TWO_PWR_24_ = Timestamp.fromInt(1 << 24); |
| 849 | ||
| 850 | /** | |
| 851 | * Expose. | |
| 852 | */ | |
| 853 | 1 | exports.Timestamp = Timestamp; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var format = require('util').format; |
| 2 | ||
| 3 | 1 | var MongoAuthProcess = function(host, port, service_name) { |
| 4 | // Check what system we are on | |
| 5 | 0 | if(process.platform == 'win32') { |
| 6 | 0 | this._processor = new Win32MongoProcessor(host, port, service_name); |
| 7 | } else { | |
| 8 | 0 | this._processor = new UnixMongoProcessor(host, port, service_name); |
| 9 | } | |
| 10 | } | |
| 11 | ||
| 12 | 1 | MongoAuthProcess.prototype.init = function(username, password, callback) { |
| 13 | 0 | this._processor.init(username, password, callback); |
| 14 | } | |
| 15 | ||
| 16 | 1 | MongoAuthProcess.prototype.transition = function(payload, callback) { |
| 17 | 0 | this._processor.transition(payload, callback); |
| 18 | } | |
| 19 | ||
| 20 | /******************************************************************* | |
| 21 | * | |
| 22 | * Win32 SSIP Processor for MongoDB | |
| 23 | * | |
| 24 | *******************************************************************/ | |
| 25 | 1 | var Win32MongoProcessor = function(host, port, service_name) { |
| 26 | 0 | this.host = host; |
| 27 | 0 | this.port = port |
| 28 | // SSIP classes | |
| 29 | 0 | this.ssip = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/node_modules/kerberos/lib/auth_processes/../kerberos").SSIP; |
| 30 | // Set up first transition | |
| 31 | 0 | this._transition = Win32MongoProcessor.first_transition(this); |
| 32 | // Set up service name | |
| 33 | 0 | service_name = service_name || "mongodb"; |
| 34 | // Set up target | |
| 35 | 0 | this.target = format("%s/%s", service_name, host); |
| 36 | // Number of retries | |
| 37 | 0 | this.retries = 10; |
| 38 | } | |
| 39 | ||
| 40 | 1 | Win32MongoProcessor.prototype.init = function(username, password, callback) { |
| 41 | 0 | var self = this; |
| 42 | // Save the values used later | |
| 43 | 0 | this.username = username; |
| 44 | 0 | this.password = password; |
| 45 | // Aquire credentials | |
| 46 | 0 | this.ssip.SecurityCredentials.aquire_kerberos(username, password, function(err, security_credentials) { |
| 47 | 0 | if(err) return callback(err); |
| 48 | // Save credentials | |
| 49 | 0 | self.security_credentials = security_credentials; |
| 50 | // Callback with success | |
| 51 | 0 | callback(null); |
| 52 | }); | |
| 53 | } | |
| 54 | ||
| 55 | 1 | Win32MongoProcessor.prototype.transition = function(payload, callback) { |
| 56 | 0 | if(this._transition == null) return callback(new Error("Transition finished")); |
| 57 | 0 | this._transition(payload, callback); |
| 58 | } | |
| 59 | ||
| 60 | 1 | Win32MongoProcessor.first_transition = function(self) { |
| 61 | 0 | return function(payload, callback) { |
| 62 | 0 | self.ssip.SecurityContext.initialize( |
| 63 | self.security_credentials, | |
| 64 | self.target, | |
| 65 | payload, function(err, security_context) { | |
| 66 | 0 | if(err) return callback(err); |
| 67 | ||
| 68 | // If no context try again until we have no more retries | |
| 69 | 0 | if(!security_context.hasContext) { |
| 70 | 0 | if(self.retries == 0) return callback(new Error("Failed to initialize security context")); |
| 71 | // Update the number of retries | |
| 72 | 0 | self.retries = self.retries - 1; |
| 73 | // Set next transition | |
| 74 | 0 | return self.transition(payload, callback); |
| 75 | } | |
| 76 | ||
| 77 | // Set next transition | |
| 78 | 0 | self._transition = Win32MongoProcessor.second_transition(self); |
| 79 | 0 | self.security_context = security_context; |
| 80 | // Return the payload | |
| 81 | 0 | callback(null, security_context.payload); |
| 82 | }); | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | 1 | Win32MongoProcessor.second_transition = function(self) { |
| 87 | 0 | return function(payload, callback) { |
| 88 | // Perform a step | |
| 89 | 0 | self.security_context.initialize(self.target, payload, function(err, security_context) { |
| 90 | 0 | if(err) return callback(err); |
| 91 | ||
| 92 | // If no context try again until we have no more retries | |
| 93 | 0 | if(!security_context.hasContext) { |
| 94 | 0 | if(self.retries == 0) return callback(new Error("Failed to initialize security context")); |
| 95 | // Update the number of retries | |
| 96 | 0 | self.retries = self.retries - 1; |
| 97 | // Set next transition | |
| 98 | 0 | self._transition = Win32MongoProcessor.first_transition(self); |
| 99 | // Retry | |
| 100 | 0 | return self.transition(payload, callback); |
| 101 | } | |
| 102 | ||
| 103 | // Set next transition | |
| 104 | 0 | self._transition = Win32MongoProcessor.third_transition(self); |
| 105 | // Return the payload | |
| 106 | 0 | callback(null, security_context.payload); |
| 107 | }); | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | 1 | Win32MongoProcessor.third_transition = function(self) { |
| 112 | 0 | return function(payload, callback) { |
| 113 | 0 | var messageLength = 0; |
| 114 | // Get the raw bytes | |
| 115 | 0 | var encryptedBytes = new Buffer(payload, 'base64'); |
| 116 | 0 | var encryptedMessage = new Buffer(messageLength); |
| 117 | // Copy first byte | |
| 118 | 0 | encryptedBytes.copy(encryptedMessage, 0, 0, messageLength); |
| 119 | // Set up trailer | |
| 120 | 0 | var securityTrailerLength = encryptedBytes.length - messageLength; |
| 121 | 0 | var securityTrailer = new Buffer(securityTrailerLength); |
| 122 | // Copy the bytes | |
| 123 | 0 | encryptedBytes.copy(securityTrailer, 0, messageLength, securityTrailerLength); |
| 124 | ||
| 125 | // Types used | |
| 126 | 0 | var SecurityBuffer = self.ssip.SecurityBuffer; |
| 127 | 0 | var SecurityBufferDescriptor = self.ssip.SecurityBufferDescriptor; |
| 128 | ||
| 129 | // Set up security buffers | |
| 130 | 0 | var buffers = [ |
| 131 | new SecurityBuffer(SecurityBuffer.DATA, encryptedBytes) | |
| 132 | , new SecurityBuffer(SecurityBuffer.STREAM, securityTrailer) | |
| 133 | ]; | |
| 134 | ||
| 135 | // Set up the descriptor | |
| 136 | 0 | var descriptor = new SecurityBufferDescriptor(buffers); |
| 137 | ||
| 138 | // Decrypt the data | |
| 139 | 0 | self.security_context.decryptMessage(descriptor, function(err, security_context) { |
| 140 | 0 | if(err) return callback(err); |
| 141 | ||
| 142 | 0 | var length = 4; |
| 143 | 0 | if(self.username != null) { |
| 144 | 0 | length += self.username.length; |
| 145 | } | |
| 146 | ||
| 147 | 0 | var bytesReceivedFromServer = new Buffer(length); |
| 148 | 0 | bytesReceivedFromServer[0] = 0x01; // NO_PROTECTION |
| 149 | 0 | bytesReceivedFromServer[1] = 0x00; // NO_PROTECTION |
| 150 | 0 | bytesReceivedFromServer[2] = 0x00; // NO_PROTECTION |
| 151 | 0 | bytesReceivedFromServer[3] = 0x00; // NO_PROTECTION |
| 152 | ||
| 153 | 0 | if(self.username != null) { |
| 154 | 0 | var authorization_id_bytes = new Buffer(self.username, 'utf8'); |
| 155 | 0 | authorization_id_bytes.copy(bytesReceivedFromServer, 4, 0); |
| 156 | } | |
| 157 | ||
| 158 | 0 | self.security_context.queryContextAttributes(0x00, function(err, sizes) { |
| 159 | 0 | if(err) return callback(err); |
| 160 | ||
| 161 | 0 | var buffers = [ |
| 162 | new SecurityBuffer(SecurityBuffer.TOKEN, new Buffer(sizes.securityTrailer)) | |
| 163 | , new SecurityBuffer(SecurityBuffer.DATA, bytesReceivedFromServer) | |
| 164 | , new SecurityBuffer(SecurityBuffer.PADDING, new Buffer(sizes.blockSize)) | |
| 165 | ] | |
| 166 | ||
| 167 | 0 | var descriptor = new SecurityBufferDescriptor(buffers); |
| 168 | ||
| 169 | 0 | self.security_context.encryptMessage(descriptor, 0x80000001, function(err, security_context) { |
| 170 | 0 | if(err) return callback(err); |
| 171 | 0 | callback(null, security_context.payload); |
| 172 | }); | |
| 173 | }); | |
| 174 | }); | |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | /******************************************************************* | |
| 179 | * | |
| 180 | * UNIX MIT Kerberos processor | |
| 181 | * | |
| 182 | *******************************************************************/ | |
| 183 | 1 | var UnixMongoProcessor = function(host, port, service_name) { |
| 184 | 0 | this.host = host; |
| 185 | 0 | this.port = port |
| 186 | // SSIP classes | |
| 187 | 0 | this.Kerberos = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mockgoose/node_modules/mongodb/node_modules/kerberos/lib/auth_processes/../kerberos").Kerberos; |
| 188 | 0 | this.kerberos = new this.Kerberos(); |
| 189 | 0 | service_name = service_name || "mongodb"; |
| 190 | // Set up first transition | |
| 191 | 0 | this._transition = UnixMongoProcessor.first_transition(this); |
| 192 | // Set up target | |
| 193 | 0 | this.target = format("%s@%s", service_name, host); |
| 194 | // Number of retries | |
| 195 | 0 | this.retries = 10; |
| 196 | } | |
| 197 | ||
| 198 | 1 | UnixMongoProcessor.prototype.init = function(username, password, callback) { |
| 199 | 0 | var self = this; |
| 200 | 0 | this.username = username; |
| 201 | 0 | this.password = password; |
| 202 | // Call client initiate | |
| 203 | 0 | this.kerberos.authGSSClientInit( |
| 204 | self.target | |
| 205 | , this.Kerberos.GSS_C_MUTUAL_FLAG, function(err, context) { | |
| 206 | 0 | self.context = context; |
| 207 | // Return the context | |
| 208 | 0 | callback(null, context); |
| 209 | }); | |
| 210 | } | |
| 211 | ||
| 212 | 1 | UnixMongoProcessor.prototype.transition = function(payload, callback) { |
| 213 | 0 | if(this._transition == null) return callback(new Error("Transition finished")); |
| 214 | 0 | this._transition(payload, callback); |
| 215 | } | |
| 216 | ||
| 217 | 1 | UnixMongoProcessor.first_transition = function(self) { |
| 218 | 0 | return function(payload, callback) { |
| 219 | 0 | self.kerberos.authGSSClientStep(self.context, '', function(err, result) { |
| 220 | 0 | if(err) return callback(err); |
| 221 | // Set up the next step | |
| 222 | 0 | self._transition = UnixMongoProcessor.second_transition(self); |
| 223 | // Return the payload | |
| 224 | 0 | callback(null, self.context.response); |
| 225 | }) | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | 1 | UnixMongoProcessor.second_transition = function(self) { |
| 230 | 0 | return function(payload, callback) { |
| 231 | 0 | self.kerberos.authGSSClientStep(self.context, payload, function(err, result) { |
| 232 | 0 | if(err && self.retries == 0) return callback(err); |
| 233 | // Attempt to re-establish a context | |
| 234 | 0 | if(err) { |
| 235 | // Adjust the number of retries | |
| 236 | 0 | self.retries = self.retries - 1; |
| 237 | // Call same step again | |
| 238 | 0 | return self.transition(payload, callback); |
| 239 | } | |
| 240 | ||
| 241 | // Set up the next step | |
| 242 | 0 | self._transition = UnixMongoProcessor.third_transition(self); |
| 243 | // Return the payload | |
| 244 | 0 | callback(null, self.context.response || ''); |
| 245 | }); | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | 1 | UnixMongoProcessor.third_transition = function(self) { |
| 250 | 0 | return function(payload, callback) { |
| 251 | // GSS Client Unwrap | |
| 252 | 0 | self.kerberos.authGSSClientUnwrap(self.context, payload, function(err, result) { |
| 253 | 0 | if(err) return callback(err, false); |
| 254 | ||
| 255 | // Wrap the response | |
| 256 | 0 | self.kerberos.authGSSClientWrap(self.context, self.context.response, self.username, function(err, result) { |
| 257 | 0 | if(err) return callback(err, false); |
| 258 | // Set up the next step | |
| 259 | 0 | self._transition = UnixMongoProcessor.fourth_transition(self); |
| 260 | // Return the payload | |
| 261 | 0 | callback(null, self.context.response); |
| 262 | }); | |
| 263 | }); | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | 1 | UnixMongoProcessor.fourth_transition = function(self) { |
| 268 | 0 | return function(payload, callback) { |
| 269 | // Clean up context | |
| 270 | 0 | self.kerberos.authGSSClientClean(self.context, function(err, result) { |
| 271 | 0 | if(err) return callback(err, false); |
| 272 | // Set the transition to null | |
| 273 | 0 | self._transition = null; |
| 274 | // Callback with valid authentication | |
| 275 | 0 | callback(null, true); |
| 276 | }); | |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | // Set the process | |
| 281 | 1 | exports.MongoAuthProcess = MongoAuthProcess; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var kerberos = require('../build/Release/kerberos') |
| 2 | , KerberosNative = kerberos.Kerberos; | |
| 3 | ||
| 4 | 1 | var Kerberos = function() { |
| 5 | 0 | this._native_kerberos = new KerberosNative(); |
| 6 | } | |
| 7 | ||
| 8 | 1 | Kerberos.prototype.authGSSClientInit = function(uri, flags, callback) { |
| 9 | 0 | return this._native_kerberos.authGSSClientInit(uri, flags, callback); |
| 10 | } | |
| 11 | ||
| 12 | 1 | Kerberos.prototype.authGSSClientStep = function(context, challenge, callback) { |
| 13 | 0 | if(typeof challenge == 'function') { |
| 14 | 0 | callback = challenge; |
| 15 | 0 | challenge = ''; |
| 16 | } | |
| 17 | ||
| 18 | 0 | return this._native_kerberos.authGSSClientStep(context, challenge, callback); |
| 19 | } | |
| 20 | ||
| 21 | 1 | Kerberos.prototype.authGSSClientUnwrap = function(context, challenge, callback) { |
| 22 | 0 | if(typeof challenge == 'function') { |
| 23 | 0 | callback = challenge; |
| 24 | 0 | challenge = ''; |
| 25 | } | |
| 26 | ||
| 27 | 0 | return this._native_kerberos.authGSSClientUnwrap(context, challenge, callback); |
| 28 | } | |
| 29 | ||
| 30 | 1 | Kerberos.prototype.authGSSClientWrap = function(context, challenge, user_name, callback) { |
| 31 | 0 | if(typeof user_name == 'function') { |
| 32 | 0 | callback = user_name; |
| 33 | 0 | user_name = ''; |
| 34 | } | |
| 35 | ||
| 36 | 0 | return this._native_kerberos.authGSSClientWrap(context, challenge, user_name, callback); |
| 37 | } | |
| 38 | ||
| 39 | 1 | Kerberos.prototype.authGSSClientClean = function(context, callback) { |
| 40 | 0 | return this._native_kerberos.authGSSClientClean(context, callback); |
| 41 | } | |
| 42 | ||
| 43 | 1 | Kerberos.prototype.acquireAlternateCredentials = function(user_name, password, domain) { |
| 44 | 0 | return this._native_kerberos.acquireAlternateCredentials(user_name, password, domain); |
| 45 | } | |
| 46 | ||
| 47 | 1 | Kerberos.prototype.prepareOutboundPackage = function(principal, inputdata) { |
| 48 | 0 | return this._native_kerberos.prepareOutboundPackage(principal, inputdata); |
| 49 | } | |
| 50 | ||
| 51 | 1 | Kerberos.prototype.decryptMessage = function(challenge) { |
| 52 | 0 | return this._native_kerberos.decryptMessage(challenge); |
| 53 | } | |
| 54 | ||
| 55 | 1 | Kerberos.prototype.encryptMessage = function(challenge) { |
| 56 | 0 | return this._native_kerberos.encryptMessage(challenge); |
| 57 | } | |
| 58 | ||
| 59 | 1 | Kerberos.prototype.queryContextAttribute = function(attribute) { |
| 60 | 0 | if(typeof attribute != 'number' && attribute != 0x00) throw new Error("Attribute not supported"); |
| 61 | 0 | return this._native_kerberos.queryContextAttribute(attribute); |
| 62 | } | |
| 63 | ||
| 64 | // Some useful result codes | |
| 65 | 1 | Kerberos.AUTH_GSS_CONTINUE = 0; |
| 66 | 1 | Kerberos.AUTH_GSS_COMPLETE = 1; |
| 67 | ||
| 68 | // Some useful gss flags | |
| 69 | 1 | Kerberos.GSS_C_DELEG_FLAG = 1; |
| 70 | 1 | Kerberos.GSS_C_MUTUAL_FLAG = 2; |
| 71 | 1 | Kerberos.GSS_C_REPLAY_FLAG = 4; |
| 72 | 1 | Kerberos.GSS_C_SEQUENCE_FLAG = 8; |
| 73 | 1 | Kerberos.GSS_C_CONF_FLAG = 16; |
| 74 | 1 | Kerberos.GSS_C_INTEG_FLAG = 32; |
| 75 | 1 | Kerberos.GSS_C_ANON_FLAG = 64; |
| 76 | 1 | Kerberos.GSS_C_PROT_READY_FLAG = 128; |
| 77 | 1 | Kerberos.GSS_C_TRANS_FLAG = 256; |
| 78 | ||
| 79 | // Export Kerberos class | |
| 80 | 1 | exports.Kerberos = Kerberos; |
| 81 | ||
| 82 | // If we have SSPI (windows) | |
| 83 | 1 | if(kerberos.SecurityCredentials) { |
| 84 | // Put all SSPI classes in it's own namespace | |
| 85 | 0 | exports.SSIP = { |
| 86 | SecurityCredentials: require('./win32/wrappers/security_credentials').SecurityCredentials | |
| 87 | , SecurityContext: require('./win32/wrappers/security_context').SecurityContext | |
| 88 | , SecurityBuffer: require('./win32/wrappers/security_buffer').SecurityBuffer | |
| 89 | , SecurityBufferDescriptor: require('./win32/wrappers/security_buffer_descriptor').SecurityBufferDescriptor | |
| 90 | } | |
| 91 | } | |
| 92 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Promise = require('./promise') |
| 6 | , util = require('util') | |
| 7 | , utils = require('./utils') | |
| 8 | , Query = require('./query') | |
| 9 | , read = Query.prototype.read | |
| 10 | ||
| 11 | /** | |
| 12 | * Aggregate constructor used for building aggregation pipelines. | |
| 13 | * | |
| 14 | * ####Example: | |
| 15 | * | |
| 16 | * new Aggregate(); | |
| 17 | * new Aggregate({ $project: { a: 1, b: 1 } }); | |
| 18 | * new Aggregate({ $project: { a: 1, b: 1 } }, { $skip: 5 }); | |
| 19 | * new Aggregate([{ $project: { a: 1, b: 1 } }, { $skip: 5 }]); | |
| 20 | * | |
| 21 | * Returned when calling Model.aggregate(). | |
| 22 | * | |
| 23 | * ####Example: | |
| 24 | * | |
| 25 | * Model | |
| 26 | * .aggregate({ $match: { age: { $gte: 21 }}}) | |
| 27 | * .unwind('tags') | |
| 28 | * .exec(callback) | |
| 29 | * | |
| 30 | * ####Note: | |
| 31 | * | |
| 32 | * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned). | |
| 33 | * - Requires MongoDB >= 2.1 | |
| 34 | * | |
| 35 | * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/ | |
| 36 | * @see driver http://mongodb.github.com/node-mongodb-native/api-generated/collection.html#aggregate | |
| 37 | * @param {Object|Array} [ops] aggregation operator(s) or operator array | |
| 38 | * @api public | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | function Aggregate () { |
| 42 | 0 | this._pipeline = []; |
| 43 | 0 | this._model = undefined; |
| 44 | 0 | this.options = undefined; |
| 45 | ||
| 46 | 0 | if (1 === arguments.length && util.isArray(arguments[0])) { |
| 47 | 0 | this.append.apply(this, arguments[0]); |
| 48 | } else { | |
| 49 | 0 | this.append.apply(this, arguments); |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | /** | |
| 54 | * Binds this aggregate to a model. | |
| 55 | * | |
| 56 | * @param {Model} model the model to which the aggregate is to be bound | |
| 57 | * @return {Aggregate} | |
| 58 | * @api private | |
| 59 | */ | |
| 60 | ||
| 61 | 1 | Aggregate.prototype.bind = function (model) { |
| 62 | 0 | this._model = model; |
| 63 | 0 | return this; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Appends new operators to this aggregate pipeline | |
| 68 | * | |
| 69 | * ####Examples: | |
| 70 | * | |
| 71 | * aggregate.append({ $project: { field: 1 }}, { $limit: 2 }); | |
| 72 | * | |
| 73 | * // or pass an array | |
| 74 | * var pipeline = [{ $match: { daw: 'Logic Audio X' }} ]; | |
| 75 | * aggregate.append(pipeline); | |
| 76 | * | |
| 77 | * @param {Object} ops operator(s) to append | |
| 78 | * @return {Aggregate} | |
| 79 | * @api public | |
| 80 | */ | |
| 81 | ||
| 82 | 1 | Aggregate.prototype.append = function () { |
| 83 | 0 | var args = utils.args(arguments) |
| 84 | , arg; | |
| 85 | ||
| 86 | 0 | if (!args.every(isOperator)) { |
| 87 | 0 | throw new Error("Arguments must be aggregate pipeline operators"); |
| 88 | } | |
| 89 | ||
| 90 | 0 | this._pipeline = this._pipeline.concat(args); |
| 91 | ||
| 92 | 0 | return this; |
| 93 | } | |
| 94 | ||
| 95 | /** | |
| 96 | * Appends a new $project operator to this aggregate pipeline. | |
| 97 | * | |
| 98 | * Mongoose query [selection syntax](#query_Query-select) is also supported. | |
| 99 | * | |
| 100 | * ####Examples: | |
| 101 | * | |
| 102 | * // include a, include b, exclude _id | |
| 103 | * aggregate.project("a b -_id"); | |
| 104 | * | |
| 105 | * // or you may use object notation, useful when | |
| 106 | * // you have keys already prefixed with a "-" | |
| 107 | * aggregate.project({a: 1, b: 1, _id: 0}); | |
| 108 | * | |
| 109 | * // reshaping documents | |
| 110 | * aggregate.project({ | |
| 111 | * newField: '$b.nested' | |
| 112 | * , plusTen: { $add: ['$val', 10]} | |
| 113 | * , sub: { | |
| 114 | * name: '$a' | |
| 115 | * } | |
| 116 | * }) | |
| 117 | * | |
| 118 | * // etc | |
| 119 | * aggregate.project({ salary_k: { $divide: [ "$salary", 1000 ] } }); | |
| 120 | * | |
| 121 | * @param {Object|String} arg field specification | |
| 122 | * @see projection http://docs.mongodb.org/manual/reference/aggregation/project/ | |
| 123 | * @return {Aggregate} | |
| 124 | * @api public | |
| 125 | */ | |
| 126 | ||
| 127 | 1 | Aggregate.prototype.project = function (arg) { |
| 128 | 0 | var fields = {}; |
| 129 | ||
| 130 | 0 | if ('object' === typeof arg && !util.isArray(arg)) { |
| 131 | 0 | Object.keys(arg).forEach(function (field) { |
| 132 | 0 | fields[field] = arg[field]; |
| 133 | }); | |
| 134 | 0 | } else if (1 === arguments.length && 'string' === typeof arg) { |
| 135 | 0 | arg.split(/\s+/).forEach(function (field) { |
| 136 | 0 | if (!field) return; |
| 137 | 0 | var include = '-' == field[0] ? 0 : 1; |
| 138 | 0 | if (include === 0) field = field.substring(1); |
| 139 | 0 | fields[field] = include; |
| 140 | }); | |
| 141 | } else { | |
| 142 | 0 | throw new Error("Invalid project() argument. Must be string or object"); |
| 143 | } | |
| 144 | ||
| 145 | 0 | return this.append({ $project: fields }); |
| 146 | } | |
| 147 | ||
| 148 | /** | |
| 149 | * Appends a new custom $group operator to this aggregate pipeline. | |
| 150 | * | |
| 151 | * ####Examples: | |
| 152 | * | |
| 153 | * aggregate.group({ _id: "$department" }); | |
| 154 | * | |
| 155 | * @see $group http://docs.mongodb.org/manual/reference/aggregation/group/ | |
| 156 | * @method group | |
| 157 | * @memberOf Aggregate | |
| 158 | * @param {Object} arg $group operator contents | |
| 159 | * @return {Aggregate} | |
| 160 | * @api public | |
| 161 | */ | |
| 162 | ||
| 163 | /** | |
| 164 | * Appends a new custom $match operator to this aggregate pipeline. | |
| 165 | * | |
| 166 | * ####Examples: | |
| 167 | * | |
| 168 | * aggregate.match({ department: { $in: [ "sales", "engineering" } } }); | |
| 169 | * | |
| 170 | * @see $match http://docs.mongodb.org/manual/reference/aggregation/match/ | |
| 171 | * @method match | |
| 172 | * @memberOf Aggregate | |
| 173 | * @param {Object} arg $match operator contents | |
| 174 | * @return {Aggregate} | |
| 175 | * @api public | |
| 176 | */ | |
| 177 | ||
| 178 | /** | |
| 179 | * Appends a new $skip operator to this aggregate pipeline. | |
| 180 | * | |
| 181 | * ####Examples: | |
| 182 | * | |
| 183 | * aggregate.skip(10); | |
| 184 | * | |
| 185 | * @see $skip http://docs.mongodb.org/manual/reference/aggregation/skip/ | |
| 186 | * @method skip | |
| 187 | * @memberOf Aggregate | |
| 188 | * @param {Number} num number of records to skip before next stage | |
| 189 | * @return {Aggregate} | |
| 190 | * @api public | |
| 191 | */ | |
| 192 | ||
| 193 | /** | |
| 194 | * Appends a new $limit operator to this aggregate pipeline. | |
| 195 | * | |
| 196 | * ####Examples: | |
| 197 | * | |
| 198 | * aggregate.limit(10); | |
| 199 | * | |
| 200 | * @see $limit http://docs.mongodb.org/manual/reference/aggregation/limit/ | |
| 201 | * @method limit | |
| 202 | * @memberOf Aggregate | |
| 203 | * @param {Number} num maximum number of records to pass to the next stage | |
| 204 | * @return {Aggregate} | |
| 205 | * @api public | |
| 206 | */ | |
| 207 | ||
| 208 | /** | |
| 209 | * Appends a new $geoNear operator to this aggregate pipeline. | |
| 210 | * | |
| 211 | * ####NOTE: | |
| 212 | * | |
| 213 | * **MUST** be used as the first operator in the pipeline. | |
| 214 | * | |
| 215 | * ####Examples: | |
| 216 | * | |
| 217 | * aggregate.near({ | |
| 218 | * near: [40.724, -73.997], | |
| 219 | * distanceField: "dist.calculated", // required | |
| 220 | * maxDistance: 0.008, | |
| 221 | * query: { type: "public" }, | |
| 222 | * includeLocs: "dist.location", | |
| 223 | * uniqueDocs: true, | |
| 224 | * num: 5 | |
| 225 | * }); | |
| 226 | * | |
| 227 | * @see $geoNear http://docs.mongodb.org/manual/reference/aggregation/geoNear/ | |
| 228 | * @method near | |
| 229 | * @memberOf Aggregate | |
| 230 | * @param {Object} parameters | |
| 231 | * @return {Aggregate} | |
| 232 | * @api public | |
| 233 | */ | |
| 234 | ||
| 235 | 1 | Aggregate.prototype.near = function (arg) { |
| 236 | 0 | var op = {}; |
| 237 | 0 | op.$geoNear = arg; |
| 238 | 0 | return this.append(op); |
| 239 | }; | |
| 240 | ||
| 241 | /*! | |
| 242 | * define methods | |
| 243 | */ | |
| 244 | ||
| 245 | 1 | 'group match skip limit'.split(' ').forEach(function ($operator) { |
| 246 | 4 | Aggregate.prototype[$operator] = function (arg) { |
| 247 | 0 | var op = {}; |
| 248 | 0 | op['$' + $operator] = arg; |
| 249 | 0 | return this.append(op); |
| 250 | }; | |
| 251 | }); | |
| 252 | ||
| 253 | /** | |
| 254 | * Appends new custom $unwind operator(s) to this aggregate pipeline. | |
| 255 | * | |
| 256 | * ####Examples: | |
| 257 | * | |
| 258 | * aggregate.unwind("tags"); | |
| 259 | * aggregate.unwind("a", "b", "c"); | |
| 260 | * | |
| 261 | * @see $unwind http://docs.mongodb.org/manual/reference/aggregation/unwind/ | |
| 262 | * @param {String} fields the field(s) to unwind | |
| 263 | * @return {Aggregate} | |
| 264 | * @api public | |
| 265 | */ | |
| 266 | ||
| 267 | 1 | Aggregate.prototype.unwind = function () { |
| 268 | 0 | var args = utils.args(arguments); |
| 269 | ||
| 270 | 0 | return this.append.apply(this, args.map(function (arg) { |
| 271 | 0 | return { $unwind: '$' + arg }; |
| 272 | })); | |
| 273 | } | |
| 274 | ||
| 275 | /** | |
| 276 | * Appends a new $sort operator to this aggregate pipeline. | |
| 277 | * | |
| 278 | * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. | |
| 279 | * | |
| 280 | * If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending. | |
| 281 | * | |
| 282 | * ####Examples: | |
| 283 | * | |
| 284 | * // these are equivalent | |
| 285 | * aggregate.sort({ field: 'asc', test: -1 }); | |
| 286 | * aggregate.sort('field -test'); | |
| 287 | * | |
| 288 | * @see $sort http://docs.mongodb.org/manual/reference/aggregation/sort/ | |
| 289 | * @param {Object|String} arg | |
| 290 | * @return {Query} this | |
| 291 | * @api public | |
| 292 | */ | |
| 293 | ||
| 294 | 1 | Aggregate.prototype.sort = function (arg) { |
| 295 | // TODO refactor to reuse the query builder logic | |
| 296 | ||
| 297 | 0 | var sort = {}; |
| 298 | ||
| 299 | 0 | if ('Object' === arg.constructor.name) { |
| 300 | 0 | var desc = ['desc', 'descending', -1]; |
| 301 | 0 | Object.keys(arg).forEach(function (field) { |
| 302 | 0 | sort[field] = desc.indexOf(arg[field]) === -1 ? 1 : -1; |
| 303 | }); | |
| 304 | 0 | } else if (1 === arguments.length && 'string' == typeof arg) { |
| 305 | 0 | arg.split(/\s+/).forEach(function (field) { |
| 306 | 0 | if (!field) return; |
| 307 | 0 | var ascend = '-' == field[0] ? -1 : 1; |
| 308 | 0 | if (ascend === -1) field = field.substring(1); |
| 309 | 0 | sort[field] = ascend; |
| 310 | }); | |
| 311 | } else { | |
| 312 | 0 | throw new TypeError('Invalid sort() argument. Must be a string or object.'); |
| 313 | } | |
| 314 | ||
| 315 | 0 | return this.append({ $sort: sort }); |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * Sets the readPreference option for the aggregation query. | |
| 320 | * | |
| 321 | * ####Example: | |
| 322 | * | |
| 323 | * Model.aggregate(..).read('primaryPreferred').exec(callback) | |
| 324 | * | |
| 325 | * @param {String} pref one of the listed preference options or their aliases | |
| 326 | * @param {Array} [tags] optional tags for this query | |
| 327 | * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference | |
| 328 | * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences | |
| 329 | */ | |
| 330 | ||
| 331 | 1 | Aggregate.prototype.read = function (pref) { |
| 332 | 0 | if (!this.options) this.options = {}; |
| 333 | 0 | read.apply(this, arguments); |
| 334 | 0 | return this; |
| 335 | } | |
| 336 | ||
| 337 | /** | |
| 338 | * Executes the aggregate pipeline on the currently bound Model. | |
| 339 | * | |
| 340 | * ####Example: | |
| 341 | * | |
| 342 | * aggregate.exec(callback); | |
| 343 | * | |
| 344 | * // Because a promise is returned, the `callback` is optional. | |
| 345 | * var promise = aggregate.exec(); | |
| 346 | * promise.then(..); | |
| 347 | * | |
| 348 | * @see Promise #promise_Promise | |
| 349 | * @param {Function} [callback] | |
| 350 | * @return {Promise} | |
| 351 | * @api public | |
| 352 | */ | |
| 353 | ||
| 354 | 1 | Aggregate.prototype.exec = function (callback) { |
| 355 | 0 | var promise = new Promise(); |
| 356 | ||
| 357 | 0 | if (callback) { |
| 358 | 0 | promise.addBack(callback); |
| 359 | } | |
| 360 | ||
| 361 | 0 | if (!this._pipeline.length) { |
| 362 | 0 | promise.error(new Error("Aggregate has empty pipeline")); |
| 363 | 0 | return promise; |
| 364 | } | |
| 365 | ||
| 366 | 0 | if (!this._model) { |
| 367 | 0 | promise.error(new Error("Aggregate not bound to any Model")); |
| 368 | 0 | return promise; |
| 369 | } | |
| 370 | ||
| 371 | 0 | this._model |
| 372 | .collection | |
| 373 | .aggregate(this._pipeline, this.options || {}, promise.resolve.bind(promise)); | |
| 374 | ||
| 375 | 0 | return promise; |
| 376 | } | |
| 377 | ||
| 378 | /*! | |
| 379 | * Helpers | |
| 380 | */ | |
| 381 | ||
| 382 | /** | |
| 383 | * Checks whether an object is likely a pipeline operator | |
| 384 | * | |
| 385 | * @param {Object} obj object to check | |
| 386 | * @return {Boolean} | |
| 387 | * @api private | |
| 388 | */ | |
| 389 | ||
| 390 | 1 | function isOperator (obj) { |
| 391 | 0 | var k; |
| 392 | ||
| 393 | 0 | if ('object' !== typeof obj) { |
| 394 | 0 | return false; |
| 395 | } | |
| 396 | ||
| 397 | 0 | k = Object.keys(obj); |
| 398 | ||
| 399 | 0 | return 1 === k.length && k.some(function (key) { |
| 400 | 0 | return '$' === key[0]; |
| 401 | }); | |
| 402 | } | |
| 403 | ||
| 404 | /*! | |
| 405 | * Exports | |
| 406 | */ | |
| 407 | ||
| 408 | 1 | module.exports = Aggregate; |
| 409 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var STATES = require('./connectionstate') |
| 7 | ||
| 8 | /** | |
| 9 | * Abstract Collection constructor | |
| 10 | * | |
| 11 | * This is the base class that drivers inherit from and implement. | |
| 12 | * | |
| 13 | * @param {String} name name of the collection | |
| 14 | * @param {Connection} conn A MongooseConnection instance | |
| 15 | * @param {Object} opts optional collection options | |
| 16 | * @api public | |
| 17 | */ | |
| 18 | ||
| 19 | 1 | function Collection (name, conn, opts) { |
| 20 | 0 | if (undefined === opts) opts = {}; |
| 21 | 0 | if (undefined === opts.capped) opts.capped = {}; |
| 22 | ||
| 23 | 0 | opts.bufferCommands = undefined === opts.bufferCommands |
| 24 | ? true | |
| 25 | : opts.bufferCommands; | |
| 26 | ||
| 27 | 0 | if ('number' == typeof opts.capped) { |
| 28 | 0 | opts.capped = { size: opts.capped }; |
| 29 | } | |
| 30 | ||
| 31 | 0 | this.opts = opts; |
| 32 | 0 | this.name = name; |
| 33 | 0 | this.conn = conn; |
| 34 | 0 | this.queue = []; |
| 35 | 0 | this.buffer = this.opts.bufferCommands; |
| 36 | ||
| 37 | 0 | if (STATES.connected == this.conn.readyState) { |
| 38 | 0 | this.onOpen(); |
| 39 | } | |
| 40 | }; | |
| 41 | ||
| 42 | /** | |
| 43 | * The collection name | |
| 44 | * | |
| 45 | * @api public | |
| 46 | * @property name | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | Collection.prototype.name; |
| 50 | ||
| 51 | /** | |
| 52 | * The Connection instance | |
| 53 | * | |
| 54 | * @api public | |
| 55 | * @property conn | |
| 56 | */ | |
| 57 | ||
| 58 | 1 | Collection.prototype.conn; |
| 59 | ||
| 60 | /** | |
| 61 | * Called when the database connects | |
| 62 | * | |
| 63 | * @api private | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | Collection.prototype.onOpen = function () { |
| 67 | 0 | var self = this; |
| 68 | 0 | this.buffer = false; |
| 69 | 0 | self.doQueue(); |
| 70 | }; | |
| 71 | ||
| 72 | /** | |
| 73 | * Called when the database disconnects | |
| 74 | * | |
| 75 | * @api private | |
| 76 | */ | |
| 77 | ||
| 78 | 1 | Collection.prototype.onClose = function () { |
| 79 | 0 | if (this.opts.bufferCommands) { |
| 80 | 0 | this.buffer = true; |
| 81 | } | |
| 82 | }; | |
| 83 | ||
| 84 | /** | |
| 85 | * Queues a method for later execution when its | |
| 86 | * database connection opens. | |
| 87 | * | |
| 88 | * @param {String} name name of the method to queue | |
| 89 | * @param {Array} args arguments to pass to the method when executed | |
| 90 | * @api private | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | Collection.prototype.addQueue = function (name, args) { |
| 94 | 0 | this.queue.push([name, args]); |
| 95 | 0 | return this; |
| 96 | }; | |
| 97 | ||
| 98 | /** | |
| 99 | * Executes all queued methods and clears the queue. | |
| 100 | * | |
| 101 | * @api private | |
| 102 | */ | |
| 103 | ||
| 104 | 1 | Collection.prototype.doQueue = function () { |
| 105 | 0 | for (var i = 0, l = this.queue.length; i < l; i++){ |
| 106 | 0 | this[this.queue[i][0]].apply(this, this.queue[i][1]); |
| 107 | } | |
| 108 | 0 | this.queue = []; |
| 109 | 0 | return this; |
| 110 | }; | |
| 111 | ||
| 112 | /** | |
| 113 | * Abstract method that drivers must implement. | |
| 114 | */ | |
| 115 | ||
| 116 | 1 | Collection.prototype.ensureIndex = function(){ |
| 117 | 0 | throw new Error('Collection#ensureIndex unimplemented by driver'); |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Abstract method that drivers must implement. | |
| 122 | */ | |
| 123 | ||
| 124 | 1 | Collection.prototype.findAndModify = function(){ |
| 125 | 0 | throw new Error('Collection#findAndModify unimplemented by driver'); |
| 126 | }; | |
| 127 | ||
| 128 | /** | |
| 129 | * Abstract method that drivers must implement. | |
| 130 | */ | |
| 131 | ||
| 132 | 1 | Collection.prototype.findOne = function(){ |
| 133 | 0 | throw new Error('Collection#findOne unimplemented by driver'); |
| 134 | }; | |
| 135 | ||
| 136 | /** | |
| 137 | * Abstract method that drivers must implement. | |
| 138 | */ | |
| 139 | ||
| 140 | 1 | Collection.prototype.find = function(){ |
| 141 | 0 | throw new Error('Collection#find unimplemented by driver'); |
| 142 | }; | |
| 143 | ||
| 144 | /** | |
| 145 | * Abstract method that drivers must implement. | |
| 146 | */ | |
| 147 | ||
| 148 | 1 | Collection.prototype.insert = function(){ |
| 149 | 0 | throw new Error('Collection#insert unimplemented by driver'); |
| 150 | }; | |
| 151 | ||
| 152 | /** | |
| 153 | * Abstract method that drivers must implement. | |
| 154 | */ | |
| 155 | ||
| 156 | 1 | Collection.prototype.save = function(){ |
| 157 | 0 | throw new Error('Collection#save unimplemented by driver'); |
| 158 | }; | |
| 159 | ||
| 160 | /** | |
| 161 | * Abstract method that drivers must implement. | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | Collection.prototype.update = function(){ |
| 165 | 0 | throw new Error('Collection#update unimplemented by driver'); |
| 166 | }; | |
| 167 | ||
| 168 | /** | |
| 169 | * Abstract method that drivers must implement. | |
| 170 | */ | |
| 171 | ||
| 172 | 1 | Collection.prototype.getIndexes = function(){ |
| 173 | 0 | throw new Error('Collection#getIndexes unimplemented by driver'); |
| 174 | }; | |
| 175 | ||
| 176 | /** | |
| 177 | * Abstract method that drivers must implement. | |
| 178 | */ | |
| 179 | ||
| 180 | 1 | Collection.prototype.mapReduce = function(){ |
| 181 | 0 | throw new Error('Collection#mapReduce unimplemented by driver'); |
| 182 | }; | |
| 183 | ||
| 184 | /*! | |
| 185 | * Module exports. | |
| 186 | */ | |
| 187 | ||
| 188 | 1 | module.exports = Collection; |
| 189 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var url = require('url') |
| 6 | , utils = require('./utils') | |
| 7 | , EventEmitter = require('events').EventEmitter | |
| 8 | , driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native' | |
| 9 | , Model = require('./model') | |
| 10 | , Schema = require('./schema') | |
| 11 | , Collection = require(driver + '/collection') | |
| 12 | , STATES = require('./connectionstate') | |
| 13 | , MongooseError = require('./error') | |
| 14 | , assert =require('assert') | |
| 15 | , muri = require('muri') | |
| 16 | ||
| 17 | /*! | |
| 18 | * Protocol prefix regexp. | |
| 19 | * | |
| 20 | * @api private | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | var rgxProtocol = /^(?:.)+:\/\//; |
| 24 | ||
| 25 | /** | |
| 26 | * Connection constructor | |
| 27 | * | |
| 28 | * For practical reasons, a Connection equals a Db. | |
| 29 | * | |
| 30 | * @param {Mongoose} base a mongoose instance | |
| 31 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter | |
| 32 | * @event `connecting`: Emitted when `connection.{open,openSet}()` is executed on this connection. | |
| 33 | * @event `connected`: Emitted when this connection successfully connects to the db. May be emitted _multiple_ times in `reconnected` scenarios. | |
| 34 | * @event `open`: Emitted after we `connected` and `onOpen` is executed on all of this connections models. | |
| 35 | * @event `disconnecting`: Emitted when `connection.close()` was executed. | |
| 36 | * @event `disconnected`: Emitted after getting disconnected from the db. | |
| 37 | * @event `close`: Emitted after we `disconnected` and `onClose` executed on all of this connections models. | |
| 38 | * @event `reconnected`: Emitted after we `connected` and subsequently `disconnected`, followed by successfully another successfull connection. | |
| 39 | * @event `error`: Emitted when an error occurs on this connection. | |
| 40 | * @event `fullsetup`: Emitted in a replica-set scenario, when all nodes specified in the connection string are connected. | |
| 41 | * @api public | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | function Connection (base) { |
| 45 | 1 | this.base = base; |
| 46 | 1 | this.collections = {}; |
| 47 | 1 | this.models = {}; |
| 48 | 1 | this.replica = false; |
| 49 | 1 | this.hosts = null; |
| 50 | 1 | this.host = null; |
| 51 | 1 | this.port = null; |
| 52 | 1 | this.user = null; |
| 53 | 1 | this.pass = null; |
| 54 | 1 | this.name = null; |
| 55 | 1 | this.options = null; |
| 56 | 1 | this.otherDbs = []; |
| 57 | 1 | this._readyState = STATES.disconnected; |
| 58 | 1 | this._closeCalled = false; |
| 59 | 1 | this._hasOpened = false; |
| 60 | }; | |
| 61 | ||
| 62 | /*! | |
| 63 | * Inherit from EventEmitter | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | Connection.prototype.__proto__ = EventEmitter.prototype; |
| 67 | ||
| 68 | /** | |
| 69 | * Connection ready state | |
| 70 | * | |
| 71 | * - 0 = disconnected | |
| 72 | * - 1 = connected | |
| 73 | * - 2 = connecting | |
| 74 | * - 3 = disconnecting | |
| 75 | * | |
| 76 | * Each state change emits its associated event name. | |
| 77 | * | |
| 78 | * ####Example | |
| 79 | * | |
| 80 | * conn.on('connected', callback); | |
| 81 | * conn.on('disconnected', callback); | |
| 82 | * | |
| 83 | * @property readyState | |
| 84 | * @api public | |
| 85 | */ | |
| 86 | ||
| 87 | 1 | Object.defineProperty(Connection.prototype, 'readyState', { |
| 88 | 1 | get: function(){ return this._readyState; } |
| 89 | , set: function (val) { | |
| 90 | 1 | if (!(val in STATES)) { |
| 91 | 0 | throw new Error('Invalid connection state: ' + val); |
| 92 | } | |
| 93 | ||
| 94 | 1 | if (this._readyState !== val) { |
| 95 | 1 | this._readyState = val; |
| 96 | // loop over the otherDbs on this connection and change their state | |
| 97 | 1 | for (var i=0; i < this.otherDbs.length; i++) { |
| 98 | 0 | this.otherDbs[i].readyState = val; |
| 99 | } | |
| 100 | ||
| 101 | 1 | if (STATES.connected === val) |
| 102 | 0 | this._hasOpened = true; |
| 103 | ||
| 104 | 1 | this.emit(STATES[val]); |
| 105 | } | |
| 106 | } | |
| 107 | }); | |
| 108 | ||
| 109 | /** | |
| 110 | * A hash of the collections associated with this connection | |
| 111 | * | |
| 112 | * @property collections | |
| 113 | */ | |
| 114 | ||
| 115 | 1 | Connection.prototype.collections; |
| 116 | ||
| 117 | /** | |
| 118 | * The mongodb.Db instance, set when the connection is opened | |
| 119 | * | |
| 120 | * @property db | |
| 121 | */ | |
| 122 | ||
| 123 | 1 | Connection.prototype.db; |
| 124 | ||
| 125 | /** | |
| 126 | * Opens the connection to MongoDB. | |
| 127 | * | |
| 128 | * `options` is a hash with the following possible properties: | |
| 129 | * | |
| 130 | * db - passed to the connection db instance | |
| 131 | * server - passed to the connection server instance(s) | |
| 132 | * replset - passed to the connection ReplSet instance | |
| 133 | * user - username for authentication | |
| 134 | * pass - password for authentication | |
| 135 | * auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate) | |
| 136 | * | |
| 137 | * ####Notes: | |
| 138 | * | |
| 139 | * Mongoose forces the db option `forceServerObjectId` false and cannot be overridden. | |
| 140 | * Mongoose defaults the server `auto_reconnect` options to true which can be overridden. | |
| 141 | * See the node-mongodb-native driver instance for options that it understands. | |
| 142 | * | |
| 143 | * _Options passed take precedence over options included in connection strings._ | |
| 144 | * | |
| 145 | * @param {String} connection_string mongodb://uri or the host to which you are connecting | |
| 146 | * @param {String} [database] database name | |
| 147 | * @param {Number} [port] database port | |
| 148 | * @param {Object} [options] options | |
| 149 | * @param {Function} [callback] | |
| 150 | * @see node-mongodb-native https://github.com/mongodb/node-mongodb-native | |
| 151 | * @see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate | |
| 152 | * @api public | |
| 153 | */ | |
| 154 | ||
| 155 | 1 | Connection.prototype.open = function (host, database, port, options, callback) { |
| 156 | 1 | var self = this |
| 157 | , parsed | |
| 158 | , uri; | |
| 159 | ||
| 160 | 1 | if ('string' === typeof database) { |
| 161 | 0 | switch (arguments.length) { |
| 162 | case 2: | |
| 163 | 0 | port = 27017; |
| 164 | case 3: | |
| 165 | 0 | switch (typeof port) { |
| 166 | case 'function': | |
| 167 | 0 | callback = port, port = 27017; |
| 168 | 0 | break; |
| 169 | case 'object': | |
| 170 | 0 | options = port, port = 27017; |
| 171 | 0 | break; |
| 172 | } | |
| 173 | 0 | break; |
| 174 | case 4: | |
| 175 | 0 | if ('function' === typeof options) |
| 176 | 0 | callback = options, options = {}; |
| 177 | } | |
| 178 | } else { | |
| 179 | 1 | switch (typeof database) { |
| 180 | case 'function': | |
| 181 | 0 | callback = database, database = undefined; |
| 182 | 0 | break; |
| 183 | case 'object': | |
| 184 | 1 | options = database; |
| 185 | 1 | database = undefined; |
| 186 | 1 | callback = port; |
| 187 | 1 | break; |
| 188 | } | |
| 189 | ||
| 190 | 1 | if (!rgxProtocol.test(host)) { |
| 191 | 1 | host = 'mongodb://' + host; |
| 192 | } | |
| 193 | ||
| 194 | 1 | try { |
| 195 | 1 | parsed = muri(host); |
| 196 | } catch (err) { | |
| 197 | 0 | this.error(err, callback); |
| 198 | 0 | return this; |
| 199 | } | |
| 200 | ||
| 201 | 1 | database = parsed.db; |
| 202 | 1 | host = parsed.hosts[0].host || parsed.hosts[0].ipc; |
| 203 | 1 | port = parsed.hosts[0].port || 27017; |
| 204 | } | |
| 205 | ||
| 206 | 1 | this.options = this.parseOptions(options, parsed && parsed.options); |
| 207 | ||
| 208 | // make sure we can open | |
| 209 | 1 | if (STATES.disconnected !== this.readyState) { |
| 210 | 0 | var err = new Error('Trying to open unclosed connection.'); |
| 211 | 0 | err.state = this.readyState; |
| 212 | 0 | this.error(err, callback); |
| 213 | 0 | return this; |
| 214 | } | |
| 215 | ||
| 216 | 1 | if (!host) { |
| 217 | 0 | this.error(new Error('Missing hostname.'), callback); |
| 218 | 0 | return this; |
| 219 | } | |
| 220 | ||
| 221 | 1 | if (!database) { |
| 222 | 0 | this.error(new Error('Missing database name.'), callback); |
| 223 | 0 | return this; |
| 224 | } | |
| 225 | ||
| 226 | // authentication | |
| 227 | 1 | if (options && options.user && options.pass) { |
| 228 | 0 | this.user = options.user; |
| 229 | 0 | this.pass = options.pass; |
| 230 | ||
| 231 | 1 | } else if (parsed && parsed.auth) { |
| 232 | 0 | this.user = parsed.auth.user; |
| 233 | 0 | this.pass = parsed.auth.pass; |
| 234 | ||
| 235 | // Check hostname for user/pass | |
| 236 | 1 | } else if (/@/.test(host) && /:/.test(host.split('@')[0])) { |
| 237 | 0 | host = host.split('@'); |
| 238 | 0 | var auth = host.shift().split(':'); |
| 239 | 0 | host = host.pop(); |
| 240 | 0 | this.user = auth[0]; |
| 241 | 0 | this.pass = auth[1]; |
| 242 | ||
| 243 | } else { | |
| 244 | 1 | this.user = this.pass = undefined; |
| 245 | } | |
| 246 | ||
| 247 | 1 | this.name = database; |
| 248 | 1 | this.host = host; |
| 249 | 1 | this.port = port; |
| 250 | ||
| 251 | 1 | this._open(callback); |
| 252 | 1 | return this; |
| 253 | }; | |
| 254 | ||
| 255 | /** | |
| 256 | * Opens the connection to a replica set. | |
| 257 | * | |
| 258 | * ####Example: | |
| 259 | * | |
| 260 | * var db = mongoose.createConnection(); | |
| 261 | * db.openSet("mongodb://user:pwd@localhost:27020/testing,mongodb://example.com:27020,mongodb://localhost:27019"); | |
| 262 | * | |
| 263 | * The database name and/or auth need only be included in one URI. | |
| 264 | * The `options` is a hash which is passed to the internal driver connection object. | |
| 265 | * | |
| 266 | * Valid `options` | |
| 267 | * | |
| 268 | * db - passed to the connection db instance | |
| 269 | * server - passed to the connection server instance(s) | |
| 270 | * replset - passed to the connection ReplSetServer instance | |
| 271 | * user - username for authentication | |
| 272 | * pass - password for authentication | |
| 273 | * auth - options for authentication (see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate) | |
| 274 | * mongos - Boolean - if true, enables High Availability support for mongos | |
| 275 | * | |
| 276 | * _Options passed take precedence over options included in connection strings._ | |
| 277 | * | |
| 278 | * ####Notes: | |
| 279 | * | |
| 280 | * _If connecting to multiple mongos servers, set the `mongos` option to true._ | |
| 281 | * | |
| 282 | * conn.open('mongodb://mongosA:27501,mongosB:27501', { mongos: true }, cb); | |
| 283 | * | |
| 284 | * Mongoose forces the db option `forceServerObjectId` false and cannot be overridden. | |
| 285 | * Mongoose defaults the server `auto_reconnect` options to true which can be overridden. | |
| 286 | * See the node-mongodb-native driver instance for options that it understands. | |
| 287 | * | |
| 288 | * _Options passed take precedence over options included in connection strings._ | |
| 289 | * | |
| 290 | * @param {String} uris comma-separated mongodb:// `URI`s | |
| 291 | * @param {String} [database] database name if not included in `uris` | |
| 292 | * @param {Object} [options] passed to the internal driver | |
| 293 | * @param {Function} [callback] | |
| 294 | * @see node-mongodb-native https://github.com/mongodb/node-mongodb-native | |
| 295 | * @see http://mongodb.github.com/node-mongodb-native/api-generated/db.html#authenticate | |
| 296 | * @api public | |
| 297 | */ | |
| 298 | ||
| 299 | 1 | Connection.prototype.openSet = function (uris, database, options, callback) { |
| 300 | 0 | if (!rgxProtocol.test(uris)) { |
| 301 | 0 | uris = 'mongodb://' + uris; |
| 302 | } | |
| 303 | ||
| 304 | 0 | var self = this; |
| 305 | ||
| 306 | 0 | switch (arguments.length) { |
| 307 | case 3: | |
| 308 | 0 | switch (typeof database) { |
| 309 | case 'string': | |
| 310 | 0 | this.name = database; |
| 311 | 0 | break; |
| 312 | case 'object': | |
| 313 | 0 | callback = options; |
| 314 | 0 | options = database; |
| 315 | 0 | database = null; |
| 316 | 0 | break; |
| 317 | } | |
| 318 | ||
| 319 | 0 | if ('function' === typeof options) { |
| 320 | 0 | callback = options; |
| 321 | 0 | options = {}; |
| 322 | } | |
| 323 | 0 | break; |
| 324 | case 2: | |
| 325 | 0 | switch (typeof database) { |
| 326 | case 'string': | |
| 327 | 0 | this.name = database; |
| 328 | 0 | break; |
| 329 | case 'function': | |
| 330 | 0 | callback = database, database = null; |
| 331 | 0 | break; |
| 332 | case 'object': | |
| 333 | 0 | options = database, database = null; |
| 334 | 0 | break; |
| 335 | } | |
| 336 | } | |
| 337 | ||
| 338 | 0 | var parsed; |
| 339 | 0 | try { |
| 340 | 0 | parsed = muri(uris); |
| 341 | } catch (err) { | |
| 342 | 0 | this.error(err, callback); |
| 343 | 0 | return this; |
| 344 | } | |
| 345 | ||
| 346 | 0 | if (!this.name) { |
| 347 | 0 | this.name = parsed.db; |
| 348 | } | |
| 349 | ||
| 350 | 0 | this.hosts = parsed.hosts; |
| 351 | 0 | this.options = this.parseOptions(options, parsed && parsed.options); |
| 352 | 0 | this.replica = true; |
| 353 | ||
| 354 | 0 | if (!this.name) { |
| 355 | 0 | this.error(new Error('No database name provided for replica set'), callback); |
| 356 | 0 | return this; |
| 357 | } | |
| 358 | ||
| 359 | // authentication | |
| 360 | 0 | if (options && options.user && options.pass) { |
| 361 | 0 | this.user = options.user; |
| 362 | 0 | this.pass = options.pass; |
| 363 | ||
| 364 | 0 | } else if (parsed && parsed.auth) { |
| 365 | 0 | this.user = parsed.auth.user; |
| 366 | 0 | this.pass = parsed.auth.pass; |
| 367 | ||
| 368 | } else { | |
| 369 | 0 | this.user = this.pass = undefined; |
| 370 | } | |
| 371 | ||
| 372 | 0 | this._open(callback); |
| 373 | 0 | return this; |
| 374 | }; | |
| 375 | ||
| 376 | /** | |
| 377 | * error | |
| 378 | * | |
| 379 | * Graceful error handling, passes error to callback | |
| 380 | * if available, else emits error on the connection. | |
| 381 | * | |
| 382 | * @param {Error} err | |
| 383 | * @param {Function} callback optional | |
| 384 | * @api private | |
| 385 | */ | |
| 386 | ||
| 387 | 1 | Connection.prototype.error = function (err, callback) { |
| 388 | 0 | if (callback) return callback(err); |
| 389 | 0 | this.emit('error', err); |
| 390 | } | |
| 391 | ||
| 392 | /** | |
| 393 | * Handles opening the connection with the appropriate method based on connection type. | |
| 394 | * | |
| 395 | * @param {Function} callback | |
| 396 | * @api private | |
| 397 | */ | |
| 398 | ||
| 399 | 1 | Connection.prototype._open = function (callback) { |
| 400 | 1 | this.readyState = STATES.connecting; |
| 401 | 1 | this._closeCalled = false; |
| 402 | ||
| 403 | 1 | var self = this; |
| 404 | ||
| 405 | 1 | var method = this.replica |
| 406 | ? 'doOpenSet' | |
| 407 | : 'doOpen'; | |
| 408 | ||
| 409 | // open connection | |
| 410 | 1 | this[method](function (err) { |
| 411 | 0 | if (err) { |
| 412 | 0 | self.readyState = STATES.disconnected; |
| 413 | 0 | if (self._hasOpened) { |
| 414 | 0 | if (callback) callback(err); |
| 415 | } else { | |
| 416 | 0 | self.error(err, callback); |
| 417 | } | |
| 418 | 0 | return; |
| 419 | } | |
| 420 | ||
| 421 | 0 | self.onOpen(callback); |
| 422 | }); | |
| 423 | } | |
| 424 | ||
| 425 | /** | |
| 426 | * Called when the connection is opened | |
| 427 | * | |
| 428 | * @api private | |
| 429 | */ | |
| 430 | ||
| 431 | 1 | Connection.prototype.onOpen = function (callback) { |
| 432 | 0 | var self = this; |
| 433 | ||
| 434 | 0 | function open (err) { |
| 435 | 0 | if (err) { |
| 436 | 0 | self.readyState = STATES.disconnected; |
| 437 | 0 | if (self._hasOpened) { |
| 438 | 0 | if (callback) callback(err); |
| 439 | } else { | |
| 440 | 0 | self.error(err, callback); |
| 441 | } | |
| 442 | 0 | return; |
| 443 | } | |
| 444 | ||
| 445 | 0 | self.readyState = STATES.connected; |
| 446 | ||
| 447 | // avoid having the collection subscribe to our event emitter | |
| 448 | // to prevent 0.3 warning | |
| 449 | 0 | for (var i in self.collections) |
| 450 | 0 | self.collections[i].onOpen(); |
| 451 | ||
| 452 | 0 | callback && callback(); |
| 453 | 0 | self.emit('open'); |
| 454 | }; | |
| 455 | ||
| 456 | // re-authenticate | |
| 457 | 0 | if (self.user && self.pass) { |
| 458 | 0 | self.db.authenticate(self.user, self.pass, self.options.auth, open); |
| 459 | } | |
| 460 | else | |
| 461 | 0 | open(); |
| 462 | }; | |
| 463 | ||
| 464 | /** | |
| 465 | * Closes the connection | |
| 466 | * | |
| 467 | * @param {Function} [callback] optional | |
| 468 | * @return {Connection} self | |
| 469 | * @api public | |
| 470 | */ | |
| 471 | ||
| 472 | 1 | Connection.prototype.close = function (callback) { |
| 473 | 0 | var self = this; |
| 474 | 0 | this._closeCalled = true; |
| 475 | ||
| 476 | 0 | switch (this.readyState){ |
| 477 | case 0: // disconnected | |
| 478 | 0 | callback && callback(); |
| 479 | 0 | break; |
| 480 | ||
| 481 | case 1: // connected | |
| 482 | 0 | this.readyState = STATES.disconnecting; |
| 483 | 0 | this.doClose(function(err){ |
| 484 | 0 | if (err){ |
| 485 | 0 | self.error(err, callback); |
| 486 | } else { | |
| 487 | 0 | self.onClose(); |
| 488 | 0 | callback && callback(); |
| 489 | } | |
| 490 | }); | |
| 491 | 0 | break; |
| 492 | ||
| 493 | case 2: // connecting | |
| 494 | 0 | this.once('open', function(){ |
| 495 | 0 | self.close(callback); |
| 496 | }); | |
| 497 | 0 | break; |
| 498 | ||
| 499 | case 3: // disconnecting | |
| 500 | 0 | if (!callback) break; |
| 501 | 0 | this.once('close', function () { |
| 502 | 0 | callback(); |
| 503 | }); | |
| 504 | 0 | break; |
| 505 | } | |
| 506 | ||
| 507 | 0 | return this; |
| 508 | }; | |
| 509 | ||
| 510 | /** | |
| 511 | * Called when the connection closes | |
| 512 | * | |
| 513 | * @api private | |
| 514 | */ | |
| 515 | ||
| 516 | 1 | Connection.prototype.onClose = function () { |
| 517 | 0 | this.readyState = STATES.disconnected; |
| 518 | ||
| 519 | // avoid having the collection subscribe to our event emitter | |
| 520 | // to prevent 0.3 warning | |
| 521 | 0 | for (var i in this.collections) |
| 522 | 0 | this.collections[i].onClose(); |
| 523 | ||
| 524 | 0 | this.emit('close'); |
| 525 | }; | |
| 526 | ||
| 527 | /** | |
| 528 | * Retrieves a collection, creating it if not cached. | |
| 529 | * | |
| 530 | * Not typically needed by applications. Just talk to your collection through your model. | |
| 531 | * | |
| 532 | * @param {String} name of the collection | |
| 533 | * @param {Object} [options] optional collection options | |
| 534 | * @return {Collection} collection instance | |
| 535 | * @api public | |
| 536 | */ | |
| 537 | ||
| 538 | 1 | Connection.prototype.collection = function (name, options) { |
| 539 | 0 | if (!(name in this.collections)) |
| 540 | 0 | this.collections[name] = new Collection(name, this, options); |
| 541 | 0 | return this.collections[name]; |
| 542 | }; | |
| 543 | ||
| 544 | /** | |
| 545 | * Defines or retrieves a model. | |
| 546 | * | |
| 547 | * var mongoose = require('mongoose'); | |
| 548 | * var db = mongoose.createConnection(..); | |
| 549 | * db.model('Venue', new Schema(..)); | |
| 550 | * var Ticket = db.model('Ticket', new Schema(..)); | |
| 551 | * var Venue = db.model('Venue'); | |
| 552 | * | |
| 553 | * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._ | |
| 554 | * | |
| 555 | * ####Example: | |
| 556 | * | |
| 557 | * var schema = new Schema({ name: String }, { collection: 'actor' }); | |
| 558 | * | |
| 559 | * // or | |
| 560 | * | |
| 561 | * schema.set('collection', 'actor'); | |
| 562 | * | |
| 563 | * // or | |
| 564 | * | |
| 565 | * var collectionName = 'actor' | |
| 566 | * var M = conn.model('Actor', schema, collectionName) | |
| 567 | * | |
| 568 | * @param {String} name the model name | |
| 569 | * @param {Schema} [schema] a schema. necessary when defining a model | |
| 570 | * @param {String} [collection] name of mongodb collection (optional) if not given it will be induced from model name | |
| 571 | * @see Mongoose#model #index_Mongoose-model | |
| 572 | * @return {Model} The compiled model | |
| 573 | * @api public | |
| 574 | */ | |
| 575 | ||
| 576 | 1 | Connection.prototype.model = function (name, schema, collection) { |
| 577 | // collection name discovery | |
| 578 | 0 | if ('string' == typeof schema) { |
| 579 | 0 | collection = schema; |
| 580 | 0 | schema = false; |
| 581 | } | |
| 582 | ||
| 583 | 0 | if (utils.isObject(schema) && !(schema instanceof Schema)) { |
| 584 | 0 | schema = new Schema(schema); |
| 585 | } | |
| 586 | ||
| 587 | 0 | if (this.models[name] && !collection) { |
| 588 | // model exists but we are not subclassing with custom collection | |
| 589 | 0 | if (schema instanceof Schema && schema != this.models[name].schema) { |
| 590 | 0 | throw new MongooseError.OverwriteModelError(name); |
| 591 | } | |
| 592 | 0 | return this.models[name]; |
| 593 | } | |
| 594 | ||
| 595 | 0 | var opts = { cache: false, connection: this } |
| 596 | 0 | var model; |
| 597 | ||
| 598 | 0 | if (schema instanceof Schema) { |
| 599 | // compile a model | |
| 600 | 0 | model = this.base.model(name, schema, collection, opts) |
| 601 | ||
| 602 | // only the first model with this name is cached to allow | |
| 603 | // for one-offs with custom collection names etc. | |
| 604 | 0 | if (!this.models[name]) { |
| 605 | 0 | this.models[name] = model; |
| 606 | } | |
| 607 | ||
| 608 | 0 | model.init(); |
| 609 | 0 | return model; |
| 610 | } | |
| 611 | ||
| 612 | 0 | if (this.models[name] && collection) { |
| 613 | // subclassing current model with alternate collection | |
| 614 | 0 | model = this.models[name]; |
| 615 | 0 | schema = model.prototype.schema; |
| 616 | 0 | var sub = model.__subclass(this, schema, collection); |
| 617 | // do not cache the sub model | |
| 618 | 0 | return sub; |
| 619 | } | |
| 620 | ||
| 621 | // lookup model in mongoose module | |
| 622 | 0 | model = this.base.models[name]; |
| 623 | ||
| 624 | 0 | if (!model) { |
| 625 | 0 | throw new MongooseError.MissingSchemaError(name); |
| 626 | } | |
| 627 | ||
| 628 | 0 | if (this == model.prototype.db |
| 629 | && (!collection || collection == model.collection.name)) { | |
| 630 | // model already uses this connection. | |
| 631 | ||
| 632 | // only the first model with this name is cached to allow | |
| 633 | // for one-offs with custom collection names etc. | |
| 634 | 0 | if (!this.models[name]) { |
| 635 | 0 | this.models[name] = model; |
| 636 | } | |
| 637 | ||
| 638 | 0 | return model; |
| 639 | } | |
| 640 | ||
| 641 | 0 | return this.models[name] = model.__subclass(this, schema, collection); |
| 642 | } | |
| 643 | ||
| 644 | /** | |
| 645 | * Returns an array of model names created on this connection. | |
| 646 | * @api public | |
| 647 | * @return {Array} | |
| 648 | */ | |
| 649 | ||
| 650 | 1 | Connection.prototype.modelNames = function () { |
| 651 | 0 | return Object.keys(this.models); |
| 652 | } | |
| 653 | ||
| 654 | /** | |
| 655 | * Set profiling level. | |
| 656 | * | |
| 657 | * @param {Number|String} level either off (0), slow (1), or all (2) | |
| 658 | * @param {Number} [ms] the threshold in milliseconds above which queries will be logged when in `slow` mode. defaults to 100. | |
| 659 | * @param {Function} callback | |
| 660 | * @api public | |
| 661 | */ | |
| 662 | ||
| 663 | 1 | Connection.prototype.setProfiling = function (level, ms, callback) { |
| 664 | 0 | if (STATES.connected !== this.readyState) { |
| 665 | 0 | return this.on('open', this.setProfiling.bind(this, level, ms, callback)); |
| 666 | } | |
| 667 | ||
| 668 | 0 | if (!callback) callback = ms, ms = 100; |
| 669 | ||
| 670 | 0 | var cmd = {}; |
| 671 | ||
| 672 | 0 | switch (level) { |
| 673 | case 0: | |
| 674 | case 'off': | |
| 675 | 0 | cmd.profile = 0; |
| 676 | 0 | break; |
| 677 | case 1: | |
| 678 | case 'slow': | |
| 679 | 0 | cmd.profile = 1; |
| 680 | 0 | if ('number' !== typeof ms) { |
| 681 | 0 | ms = parseInt(ms, 10); |
| 682 | 0 | if (isNaN(ms)) ms = 100; |
| 683 | } | |
| 684 | 0 | cmd.slowms = ms; |
| 685 | 0 | break; |
| 686 | case 2: | |
| 687 | case 'all': | |
| 688 | 0 | cmd.profile = 2; |
| 689 | 0 | break; |
| 690 | default: | |
| 691 | 0 | return callback(new Error('Invalid profiling level: '+ level)); |
| 692 | } | |
| 693 | ||
| 694 | 0 | this.db.executeDbCommand(cmd, function (err, resp) { |
| 695 | 0 | if (err) return callback(err); |
| 696 | ||
| 697 | 0 | var doc = resp.documents[0]; |
| 698 | ||
| 699 | 0 | err = 1 === doc.ok |
| 700 | ? null | |
| 701 | : new Error('Could not set profiling level to: '+ level) | |
| 702 | ||
| 703 | 0 | callback(err, doc); |
| 704 | }); | |
| 705 | }; | |
| 706 | ||
| 707 | /*! | |
| 708 | * Noop. | |
| 709 | */ | |
| 710 | ||
| 711 | 1 | function noop () {} |
| 712 | ||
| 713 | /*! | |
| 714 | * Module exports. | |
| 715 | */ | |
| 716 | ||
| 717 | 1 | Connection.STATES = STATES; |
| 718 | 1 | module.exports = Connection; |
| 719 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Connection states | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var STATES = module.exports = exports = Object.create(null); |
| 7 | ||
| 8 | 1 | var disconnected = 'disconnected'; |
| 9 | 1 | var connected = 'connected'; |
| 10 | 1 | var connecting = 'connecting'; |
| 11 | 1 | var disconnecting = 'disconnecting'; |
| 12 | 1 | var uninitialized = 'uninitialized'; |
| 13 | ||
| 14 | 1 | STATES[0] = disconnected; |
| 15 | 1 | STATES[1] = connected; |
| 16 | 1 | STATES[2] = connecting; |
| 17 | 1 | STATES[3] = disconnecting; |
| 18 | 1 | STATES[99] = uninitialized; |
| 19 | ||
| 20 | 1 | STATES[disconnected] = 0; |
| 21 | 1 | STATES[connected] = 1; |
| 22 | 1 | STATES[connecting] = 2; |
| 23 | 1 | STATES[disconnecting] = 3; |
| 24 | 1 | STATES[uninitialized] = 99; |
| 25 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var EventEmitter = require('events').EventEmitter |
| 6 | , setMaxListeners = EventEmitter.prototype.setMaxListeners | |
| 7 | , MongooseError = require('./error') | |
| 8 | , MixedSchema = require('./schema/mixed') | |
| 9 | , Schema = require('./schema') | |
| 10 | , ValidatorError = require('./schematype').ValidatorError | |
| 11 | , utils = require('./utils') | |
| 12 | , clone = utils.clone | |
| 13 | , isMongooseObject = utils.isMongooseObject | |
| 14 | , inspect = require('util').inspect | |
| 15 | , ElemMatchError = MongooseError.ElemMatchError | |
| 16 | , ValidationError = MongooseError.ValidationError | |
| 17 | , InternalCache = require('./internal') | |
| 18 | , deepEqual = utils.deepEqual | |
| 19 | , hooks = require('hooks') | |
| 20 | , DocumentArray | |
| 21 | , MongooseArray | |
| 22 | , Embedded | |
| 23 | ||
| 24 | /** | |
| 25 | * Document constructor. | |
| 26 | * | |
| 27 | * @param {Object} obj the values to set | |
| 28 | * @param {Object} [opts] optional object containing the fields which were selected in the query returning this document and any populated paths data | |
| 29 | * @param {Boolean} [skipId] bool, should we auto create an ObjectId _id | |
| 30 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter | |
| 31 | * @event `init`: Emitted on a document after it has was retreived from the db and fully hydrated by Mongoose. | |
| 32 | * @event `save`: Emitted when the document is successfully saved | |
| 33 | * @api private | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | function Document (obj, fields, skipId) { |
| 37 | 0 | this.$__ = new InternalCache; |
| 38 | 0 | this.isNew = true; |
| 39 | 0 | this.errors = undefined; |
| 40 | ||
| 41 | 0 | var schema = this.schema; |
| 42 | ||
| 43 | 0 | if ('boolean' === typeof fields) { |
| 44 | 0 | this.$__.strictMode = fields; |
| 45 | 0 | fields = undefined; |
| 46 | } else { | |
| 47 | 0 | this.$__.strictMode = schema.options && schema.options.strict; |
| 48 | 0 | this.$__.selected = fields; |
| 49 | } | |
| 50 | ||
| 51 | 0 | var required = schema.requiredPaths(); |
| 52 | 0 | for (var i = 0; i < required.length; ++i) { |
| 53 | 0 | this.$__.activePaths.require(required[i]); |
| 54 | } | |
| 55 | ||
| 56 | 0 | setMaxListeners.call(this, 0); |
| 57 | 0 | this._doc = this.$__buildDoc(obj, fields, skipId); |
| 58 | ||
| 59 | 0 | if (obj) { |
| 60 | 0 | this.set(obj, undefined, true); |
| 61 | } | |
| 62 | ||
| 63 | 0 | this.$__registerHooks(); |
| 64 | } | |
| 65 | ||
| 66 | /*! | |
| 67 | * Inherit from EventEmitter. | |
| 68 | */ | |
| 69 | ||
| 70 | 1 | Document.prototype.__proto__ = EventEmitter.prototype; |
| 71 | ||
| 72 | /** | |
| 73 | * The documents schema. | |
| 74 | * | |
| 75 | * @api public | |
| 76 | * @property schema | |
| 77 | */ | |
| 78 | ||
| 79 | 1 | Document.prototype.schema; |
| 80 | ||
| 81 | /** | |
| 82 | * Boolean flag specifying if the document is new. | |
| 83 | * | |
| 84 | * @api public | |
| 85 | * @property isNew | |
| 86 | */ | |
| 87 | ||
| 88 | 1 | Document.prototype.isNew; |
| 89 | ||
| 90 | /** | |
| 91 | * The string version of this documents _id. | |
| 92 | * | |
| 93 | * ####Note: | |
| 94 | * | |
| 95 | * This getter exists on all documents by default. The getter can be disabled by setting the `id` [option](/docs/guide.html#id) of its `Schema` to false at construction time. | |
| 96 | * | |
| 97 | * new Schema({ name: String }, { id: false }); | |
| 98 | * | |
| 99 | * @api public | |
| 100 | * @see Schema options /docs/guide.html#options | |
| 101 | * @property id | |
| 102 | */ | |
| 103 | ||
| 104 | 1 | Document.prototype.id; |
| 105 | ||
| 106 | /** | |
| 107 | * Hash containing current validation errors. | |
| 108 | * | |
| 109 | * @api public | |
| 110 | * @property errors | |
| 111 | */ | |
| 112 | ||
| 113 | 1 | Document.prototype.errors; |
| 114 | ||
| 115 | /** | |
| 116 | * Builds the default doc structure | |
| 117 | * | |
| 118 | * @param {Object} obj | |
| 119 | * @param {Object} [fields] | |
| 120 | * @param {Boolean} [skipId] | |
| 121 | * @return {Object} | |
| 122 | * @api private | |
| 123 | * @method $__buildDoc | |
| 124 | * @memberOf Document | |
| 125 | */ | |
| 126 | ||
| 127 | 1 | Document.prototype.$__buildDoc = function (obj, fields, skipId) { |
| 128 | 0 | var doc = {} |
| 129 | , self = this | |
| 130 | , exclude | |
| 131 | , keys | |
| 132 | , key | |
| 133 | , ki | |
| 134 | ||
| 135 | // determine if this doc is a result of a query with | |
| 136 | // excluded fields | |
| 137 | 0 | if (fields && 'Object' === fields.constructor.name) { |
| 138 | 0 | keys = Object.keys(fields); |
| 139 | 0 | ki = keys.length; |
| 140 | ||
| 141 | 0 | while (ki--) { |
| 142 | 0 | if ('_id' !== keys[ki]) { |
| 143 | 0 | exclude = 0 === fields[keys[ki]]; |
| 144 | 0 | break; |
| 145 | } | |
| 146 | } | |
| 147 | } | |
| 148 | ||
| 149 | 0 | var paths = Object.keys(this.schema.paths) |
| 150 | , plen = paths.length | |
| 151 | , ii = 0 | |
| 152 | ||
| 153 | 0 | for (; ii < plen; ++ii) { |
| 154 | 0 | var p = paths[ii]; |
| 155 | ||
| 156 | 0 | if ('_id' == p) { |
| 157 | 0 | if (skipId) continue; |
| 158 | 0 | if (obj && '_id' in obj) continue; |
| 159 | } | |
| 160 | ||
| 161 | 0 | var type = this.schema.paths[p] |
| 162 | , path = p.split('.') | |
| 163 | , len = path.length | |
| 164 | , last = len-1 | |
| 165 | , curPath = '' | |
| 166 | , doc_ = doc | |
| 167 | , i = 0 | |
| 168 | ||
| 169 | 0 | for (; i < len; ++i) { |
| 170 | 0 | var piece = path[i] |
| 171 | , def | |
| 172 | ||
| 173 | // support excluding intermediary levels | |
| 174 | 0 | if (exclude) { |
| 175 | 0 | curPath += piece; |
| 176 | 0 | if (curPath in fields) break; |
| 177 | 0 | curPath += '.'; |
| 178 | } | |
| 179 | ||
| 180 | 0 | if (i === last) { |
| 181 | 0 | if (fields) { |
| 182 | 0 | if (exclude) { |
| 183 | // apply defaults to all non-excluded fields | |
| 184 | 0 | if (p in fields) continue; |
| 185 | ||
| 186 | 0 | def = type.getDefault(self, true); |
| 187 | 0 | if ('undefined' !== typeof def) { |
| 188 | 0 | doc_[piece] = def; |
| 189 | 0 | self.$__.activePaths.default(p); |
| 190 | } | |
| 191 | ||
| 192 | 0 | } else if (p in fields) { |
| 193 | // selected field | |
| 194 | 0 | def = type.getDefault(self, true); |
| 195 | 0 | if ('undefined' !== typeof def) { |
| 196 | 0 | doc_[piece] = def; |
| 197 | 0 | self.$__.activePaths.default(p); |
| 198 | } | |
| 199 | } | |
| 200 | } else { | |
| 201 | 0 | def = type.getDefault(self, true); |
| 202 | 0 | if ('undefined' !== typeof def) { |
| 203 | 0 | doc_[piece] = def; |
| 204 | 0 | self.$__.activePaths.default(p); |
| 205 | } | |
| 206 | } | |
| 207 | } else { | |
| 208 | 0 | doc_ = doc_[piece] || (doc_[piece] = {}); |
| 209 | } | |
| 210 | } | |
| 211 | }; | |
| 212 | ||
| 213 | 0 | return doc; |
| 214 | }; | |
| 215 | ||
| 216 | /** | |
| 217 | * Initializes the document without setters or marking anything modified. | |
| 218 | * | |
| 219 | * Called internally after a document is returned from mongodb. | |
| 220 | * | |
| 221 | * @param {Object} doc document returned by mongo | |
| 222 | * @param {Function} fn callback | |
| 223 | * @api private | |
| 224 | */ | |
| 225 | ||
| 226 | 1 | Document.prototype.init = function (doc, opts, fn) { |
| 227 | // do not prefix this method with $__ since its | |
| 228 | // used by public hooks | |
| 229 | ||
| 230 | 0 | if ('function' == typeof opts) { |
| 231 | 0 | fn = opts; |
| 232 | 0 | opts = null; |
| 233 | } | |
| 234 | ||
| 235 | 0 | this.isNew = false; |
| 236 | ||
| 237 | // handle docs with populated paths | |
| 238 | 0 | if (doc._id && opts && opts.populated && opts.populated.length) { |
| 239 | 0 | var id = String(doc._id); |
| 240 | 0 | for (var i = 0; i < opts.populated.length; ++i) { |
| 241 | 0 | var item = opts.populated[i]; |
| 242 | 0 | this.populated(item.path, item._docs[id], item); |
| 243 | } | |
| 244 | } | |
| 245 | ||
| 246 | 0 | init(this, doc, this._doc); |
| 247 | 0 | this.$__storeShard(); |
| 248 | ||
| 249 | 0 | this.emit('init', this); |
| 250 | 0 | if (fn) fn(null); |
| 251 | 0 | return this; |
| 252 | }; | |
| 253 | ||
| 254 | /*! | |
| 255 | * Init helper. | |
| 256 | * | |
| 257 | * @param {Object} self document instance | |
| 258 | * @param {Object} obj raw mongodb doc | |
| 259 | * @param {Object} doc object we are initializing | |
| 260 | * @api private | |
| 261 | */ | |
| 262 | ||
| 263 | 1 | function init (self, obj, doc, prefix) { |
| 264 | 0 | prefix = prefix || ''; |
| 265 | ||
| 266 | 0 | var keys = Object.keys(obj) |
| 267 | , len = keys.length | |
| 268 | , schema | |
| 269 | , path | |
| 270 | , i; | |
| 271 | ||
| 272 | 0 | while (len--) { |
| 273 | 0 | i = keys[len]; |
| 274 | 0 | path = prefix + i; |
| 275 | 0 | schema = self.schema.path(path); |
| 276 | ||
| 277 | 0 | if (!schema && utils.isObject(obj[i]) && |
| 278 | (!obj[i].constructor || 'Object' == obj[i].constructor.name)) { | |
| 279 | // assume nested object | |
| 280 | 0 | if (!doc[i]) doc[i] = {}; |
| 281 | 0 | init(self, obj[i], doc[i], path + '.'); |
| 282 | } else { | |
| 283 | 0 | if (obj[i] === null) { |
| 284 | 0 | doc[i] = null; |
| 285 | 0 | } else if (obj[i] !== undefined) { |
| 286 | 0 | if (schema) { |
| 287 | 0 | self.$__try(function(){ |
| 288 | 0 | doc[i] = schema.cast(obj[i], self, true); |
| 289 | }); | |
| 290 | } else { | |
| 291 | 0 | doc[i] = obj[i]; |
| 292 | } | |
| 293 | } | |
| 294 | // mark as hydrated | |
| 295 | 0 | self.$__.activePaths.init(path); |
| 296 | } | |
| 297 | } | |
| 298 | }; | |
| 299 | ||
| 300 | /** | |
| 301 | * Stores the current values of the shard keys. | |
| 302 | * | |
| 303 | * ####Note: | |
| 304 | * | |
| 305 | * _Shard key values do not / are not allowed to change._ | |
| 306 | * | |
| 307 | * @api private | |
| 308 | * @method $__storeShard | |
| 309 | * @memberOf Document | |
| 310 | */ | |
| 311 | ||
| 312 | 1 | Document.prototype.$__storeShard = function () { |
| 313 | // backwards compat | |
| 314 | 0 | var key = this.schema.options.shardKey || this.schema.options.shardkey; |
| 315 | 0 | if (!(key && 'Object' == key.constructor.name)) return; |
| 316 | ||
| 317 | 0 | var orig = this.$__.shardval = {} |
| 318 | , paths = Object.keys(key) | |
| 319 | , len = paths.length | |
| 320 | , val | |
| 321 | ||
| 322 | 0 | for (var i = 0; i < len; ++i) { |
| 323 | 0 | val = this.getValue(paths[i]); |
| 324 | 0 | if (isMongooseObject(val)) { |
| 325 | 0 | orig[paths[i]] = val.toObject({ depopulate: true }) |
| 326 | 0 | } else if (null != val && val.valueOf) { |
| 327 | 0 | orig[paths[i]] = val.valueOf(); |
| 328 | } else { | |
| 329 | 0 | orig[paths[i]] = val; |
| 330 | } | |
| 331 | } | |
| 332 | } | |
| 333 | ||
| 334 | /*! | |
| 335 | * Set up middleware support | |
| 336 | */ | |
| 337 | ||
| 338 | 1 | for (var k in hooks) { |
| 339 | 5 | Document.prototype[k] = Document[k] = hooks[k]; |
| 340 | } | |
| 341 | ||
| 342 | /** | |
| 343 | * Sends an update command with this document `_id` as the query selector. | |
| 344 | * | |
| 345 | * ####Example: | |
| 346 | * | |
| 347 | * weirdCar.update({$inc: {wheels:1}}, { w: 1 }, callback); | |
| 348 | * | |
| 349 | * ####Valid options: | |
| 350 | * | |
| 351 | * - same as in [Model.update](#model_Model.update) | |
| 352 | * | |
| 353 | * @see Model.update #model_Model.update | |
| 354 | * @param {Object} doc | |
| 355 | * @param {Object} options | |
| 356 | * @param {Function} callback | |
| 357 | * @return {Query} | |
| 358 | * @api public | |
| 359 | */ | |
| 360 | ||
| 361 | 1 | Document.prototype.update = function update () { |
| 362 | 0 | var args = utils.args(arguments); |
| 363 | 0 | args.unshift({_id: this._id}); |
| 364 | 0 | return this.constructor.update.apply(this.constructor, args); |
| 365 | } | |
| 366 | ||
| 367 | /** | |
| 368 | * Sets the value of a path, or many paths. | |
| 369 | * | |
| 370 | * ####Example: | |
| 371 | * | |
| 372 | * // path, value | |
| 373 | * doc.set(path, value) | |
| 374 | * | |
| 375 | * // object | |
| 376 | * doc.set({ | |
| 377 | * path : value | |
| 378 | * , path2 : { | |
| 379 | * path : value | |
| 380 | * } | |
| 381 | * }) | |
| 382 | * | |
| 383 | * // only-the-fly cast to number | |
| 384 | * doc.set(path, value, Number) | |
| 385 | * | |
| 386 | * // only-the-fly cast to string | |
| 387 | * doc.set(path, value, String) | |
| 388 | * | |
| 389 | * // changing strict mode behavior | |
| 390 | * doc.set(path, value, { strict: false }); | |
| 391 | * | |
| 392 | * @param {String|Object} path path or object of key/vals to set | |
| 393 | * @param {Any} val the value to set | |
| 394 | * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for "on-the-fly" attributes | |
| 395 | * @param {Object} [options] optionally specify options that modify the behavior of the set | |
| 396 | * @api public | |
| 397 | */ | |
| 398 | ||
| 399 | 1 | Document.prototype.set = function (path, val, type, options) { |
| 400 | 0 | if (type && 'Object' == type.constructor.name) { |
| 401 | 0 | options = type; |
| 402 | 0 | type = undefined; |
| 403 | } | |
| 404 | ||
| 405 | 0 | var merge = options && options.merge |
| 406 | , adhoc = type && true !== type | |
| 407 | , constructing = true === type | |
| 408 | , adhocs | |
| 409 | ||
| 410 | 0 | var strict = options && 'strict' in options |
| 411 | ? options.strict | |
| 412 | : this.$__.strictMode; | |
| 413 | ||
| 414 | 0 | if (adhoc) { |
| 415 | 0 | adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); |
| 416 | 0 | adhocs[path] = Schema.interpretAsType(path, type); |
| 417 | } | |
| 418 | ||
| 419 | 0 | if ('string' !== typeof path) { |
| 420 | // new Document({ key: val }) | |
| 421 | ||
| 422 | 0 | if (null === path || undefined === path) { |
| 423 | 0 | var _ = path; |
| 424 | 0 | path = val; |
| 425 | 0 | val = _; |
| 426 | ||
| 427 | } else { | |
| 428 | 0 | var prefix = val |
| 429 | ? val + '.' | |
| 430 | : ''; | |
| 431 | ||
| 432 | 0 | if (path instanceof Document) path = path._doc; |
| 433 | ||
| 434 | 0 | var keys = Object.keys(path) |
| 435 | , i = keys.length | |
| 436 | , pathtype | |
| 437 | , key | |
| 438 | ||
| 439 | ||
| 440 | 0 | while (i--) { |
| 441 | 0 | key = keys[i]; |
| 442 | 0 | pathtype = this.schema.pathType(prefix + key); |
| 443 | 0 | if (null != path[key] |
| 444 | // need to know if plain object - no Buffer, ObjectId, ref, etc | |
| 445 | && utils.isObject(path[key]) | |
| 446 | && (!path[key].constructor || 'Object' == path[key].constructor.name) | |
| 447 | && 'virtual' != pathtype | |
| 448 | && !(this.$__path(prefix + key) instanceof MixedSchema) | |
| 449 | && !(this.schema.paths[key] && this.schema.paths[key].options.ref) | |
| 450 | ) { | |
| 451 | 0 | this.set(path[key], prefix + key, constructing); |
| 452 | 0 | } else if (strict) { |
| 453 | 0 | if ('real' === pathtype || 'virtual' === pathtype) { |
| 454 | 0 | this.set(prefix + key, path[key], constructing); |
| 455 | 0 | } else if ('throw' == strict) { |
| 456 | 0 | throw new Error("Field `" + key + "` is not in schema."); |
| 457 | } | |
| 458 | 0 | } else if (undefined !== path[key]) { |
| 459 | 0 | this.set(prefix + key, path[key], constructing); |
| 460 | } | |
| 461 | } | |
| 462 | ||
| 463 | 0 | return this; |
| 464 | } | |
| 465 | } | |
| 466 | ||
| 467 | // ensure _strict is honored for obj props | |
| 468 | // docschema = new Schema({ path: { nest: 'string' }}) | |
| 469 | // doc.set('path', obj); | |
| 470 | 0 | var pathType = this.schema.pathType(path); |
| 471 | 0 | if ('nested' == pathType && val && utils.isObject(val) && |
| 472 | (!val.constructor || 'Object' == val.constructor.name)) { | |
| 473 | 0 | if (!merge) this.setValue(path, null); |
| 474 | 0 | this.set(val, path, constructing); |
| 475 | 0 | return this; |
| 476 | } | |
| 477 | ||
| 478 | 0 | var schema; |
| 479 | 0 | var parts = path.split('.'); |
| 480 | ||
| 481 | 0 | if ('adhocOrUndefined' == pathType && strict) { |
| 482 | ||
| 483 | // check for roots that are Mixed types | |
| 484 | 0 | var mixed; |
| 485 | ||
| 486 | 0 | for (var i = 0; i < parts.length; ++i) { |
| 487 | 0 | var subpath = parts.slice(0, i+1).join('.'); |
| 488 | 0 | schema = this.schema.path(subpath); |
| 489 | 0 | if (schema instanceof MixedSchema) { |
| 490 | // allow changes to sub paths of mixed types | |
| 491 | 0 | mixed = true; |
| 492 | 0 | break; |
| 493 | } | |
| 494 | } | |
| 495 | ||
| 496 | 0 | if (!mixed) { |
| 497 | 0 | if ('throw' == strict) { |
| 498 | 0 | throw new Error("Field `" + path + "` is not in schema."); |
| 499 | } | |
| 500 | 0 | return this; |
| 501 | } | |
| 502 | ||
| 503 | 0 | } else if ('virtual' == pathType) { |
| 504 | 0 | schema = this.schema.virtualpath(path); |
| 505 | 0 | schema.applySetters(val, this); |
| 506 | 0 | return this; |
| 507 | } else { | |
| 508 | 0 | schema = this.$__path(path); |
| 509 | } | |
| 510 | ||
| 511 | 0 | var pathToMark; |
| 512 | ||
| 513 | // When using the $set operator the path to the field must already exist. | |
| 514 | // Else mongodb throws: "LEFT_SUBFIELD only supports Object" | |
| 515 | ||
| 516 | 0 | if (parts.length <= 1) { |
| 517 | 0 | pathToMark = path; |
| 518 | } else { | |
| 519 | 0 | for (var i = 0; i < parts.length; ++i) { |
| 520 | 0 | var subpath = parts.slice(0, i+1).join('.'); |
| 521 | 0 | if (this.isDirectModified(subpath) // earlier prefixes that are already |
| 522 | // marked as dirty have precedence | |
| 523 | || this.get(subpath) === null) { | |
| 524 | 0 | pathToMark = subpath; |
| 525 | 0 | break; |
| 526 | } | |
| 527 | } | |
| 528 | ||
| 529 | 0 | if (!pathToMark) pathToMark = path; |
| 530 | } | |
| 531 | ||
| 532 | // if this doc is being constructed we should not trigger getters | |
| 533 | 0 | var priorVal = constructing |
| 534 | ? undefined | |
| 535 | : this.get(path); | |
| 536 | ||
| 537 | 0 | if (!schema || undefined === val) { |
| 538 | 0 | this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal); |
| 539 | 0 | return this; |
| 540 | } | |
| 541 | ||
| 542 | 0 | var self = this; |
| 543 | 0 | var shouldSet = this.$__try(function(){ |
| 544 | 0 | val = schema.applySetters(val, self, false, priorVal); |
| 545 | }); | |
| 546 | ||
| 547 | 0 | if (shouldSet) { |
| 548 | 0 | this.$__set(pathToMark, path, constructing, parts, schema, val, priorVal); |
| 549 | } | |
| 550 | ||
| 551 | 0 | return this; |
| 552 | } | |
| 553 | ||
| 554 | /** | |
| 555 | * Determine if we should mark this change as modified. | |
| 556 | * | |
| 557 | * @return {Boolean} | |
| 558 | * @api private | |
| 559 | * @method $__shouldModify | |
| 560 | * @memberOf Document | |
| 561 | */ | |
| 562 | ||
| 563 | 1 | Document.prototype.$__shouldModify = function ( |
| 564 | pathToMark, path, constructing, parts, schema, val, priorVal) { | |
| 565 | ||
| 566 | 0 | if (this.isNew) return true; |
| 567 | 0 | if (this.isDirectModified(pathToMark)) return false; |
| 568 | ||
| 569 | 0 | if (undefined === val && !this.isSelected(path)) { |
| 570 | // when a path is not selected in a query, its initial | |
| 571 | // value will be undefined. | |
| 572 | 0 | return true; |
| 573 | } | |
| 574 | ||
| 575 | 0 | if (undefined === val && path in this.$__.activePaths.states.default) { |
| 576 | // we're just unsetting the default value which was never saved | |
| 577 | 0 | return false; |
| 578 | } | |
| 579 | ||
| 580 | 0 | if (!deepEqual(val, priorVal || this.get(path))) { |
| 581 | 0 | return true; |
| 582 | } | |
| 583 | ||
| 584 | 0 | if (!constructing && |
| 585 | null != val && | |
| 586 | path in this.$__.activePaths.states.default && | |
| 587 | deepEqual(val, schema.getDefault(this, constructing))) { | |
| 588 | // a path with a default was $unset on the server | |
| 589 | // and the user is setting it to the same value again | |
| 590 | 0 | return true; |
| 591 | } | |
| 592 | ||
| 593 | 0 | return false; |
| 594 | } | |
| 595 | ||
| 596 | /** | |
| 597 | * Handles the actual setting of the value and marking the path modified if appropriate. | |
| 598 | * | |
| 599 | * @api private | |
| 600 | * @method $__set | |
| 601 | * @memberOf Document | |
| 602 | */ | |
| 603 | ||
| 604 | 1 | Document.prototype.$__set = function ( |
| 605 | pathToMark, path, constructing, parts, schema, val, priorVal) { | |
| 606 | ||
| 607 | 0 | var shouldModify = this.$__shouldModify.apply(this, arguments); |
| 608 | ||
| 609 | 0 | if (shouldModify) { |
| 610 | 0 | this.markModified(pathToMark, val); |
| 611 | ||
| 612 | // handle directly setting arrays (gh-1126) | |
| 613 | 0 | MongooseArray || (MongooseArray = require('./types/array')); |
| 614 | 0 | if (val instanceof MongooseArray) { |
| 615 | 0 | val._registerAtomic('$set', val); |
| 616 | } | |
| 617 | } | |
| 618 | ||
| 619 | 0 | var obj = this._doc |
| 620 | , i = 0 | |
| 621 | , l = parts.length | |
| 622 | ||
| 623 | 0 | for (; i < l; i++) { |
| 624 | 0 | var next = i + 1 |
| 625 | , last = next === l; | |
| 626 | ||
| 627 | 0 | if (last) { |
| 628 | 0 | obj[parts[i]] = val; |
| 629 | } else { | |
| 630 | 0 | if (obj[parts[i]] && 'Object' === obj[parts[i]].constructor.name) { |
| 631 | 0 | obj = obj[parts[i]]; |
| 632 | 0 | } else if (obj[parts[i]] && Array.isArray(obj[parts[i]])) { |
| 633 | 0 | obj = obj[parts[i]]; |
| 634 | } else { | |
| 635 | 0 | obj = obj[parts[i]] = {}; |
| 636 | } | |
| 637 | } | |
| 638 | } | |
| 639 | } | |
| 640 | ||
| 641 | /** | |
| 642 | * Gets a raw value from a path (no getters) | |
| 643 | * | |
| 644 | * @param {String} path | |
| 645 | * @api private | |
| 646 | */ | |
| 647 | ||
| 648 | 1 | Document.prototype.getValue = function (path) { |
| 649 | 0 | return utils.getValue(path, this._doc); |
| 650 | } | |
| 651 | ||
| 652 | /** | |
| 653 | * Sets a raw value for a path (no casting, setters, transformations) | |
| 654 | * | |
| 655 | * @param {String} path | |
| 656 | * @param {Object} value | |
| 657 | * @api private | |
| 658 | */ | |
| 659 | ||
| 660 | 1 | Document.prototype.setValue = function (path, val) { |
| 661 | 0 | utils.setValue(path, val, this._doc); |
| 662 | 0 | return this; |
| 663 | } | |
| 664 | ||
| 665 | /** | |
| 666 | * Returns the value of a path. | |
| 667 | * | |
| 668 | * ####Example | |
| 669 | * | |
| 670 | * // path | |
| 671 | * doc.get('age') // 47 | |
| 672 | * | |
| 673 | * // dynamic casting to a string | |
| 674 | * doc.get('age', String) // "47" | |
| 675 | * | |
| 676 | * @param {String} path | |
| 677 | * @param {Schema|String|Number|Buffer|etc..} [type] optionally specify a type for on-the-fly attributes | |
| 678 | * @api public | |
| 679 | */ | |
| 680 | ||
| 681 | 1 | Document.prototype.get = function (path, type) { |
| 682 | 0 | var adhocs; |
| 683 | 0 | if (type) { |
| 684 | 0 | adhocs = this.$__.adhocPaths || (this.$__.adhocPaths = {}); |
| 685 | 0 | adhocs[path] = Schema.interpretAsType(path, type); |
| 686 | } | |
| 687 | ||
| 688 | 0 | var schema = this.$__path(path) || this.schema.virtualpath(path) |
| 689 | , pieces = path.split('.') | |
| 690 | , obj = this._doc; | |
| 691 | ||
| 692 | 0 | for (var i = 0, l = pieces.length; i < l; i++) { |
| 693 | 0 | obj = undefined === obj || null === obj |
| 694 | ? undefined | |
| 695 | : obj[pieces[i]]; | |
| 696 | } | |
| 697 | ||
| 698 | 0 | if (schema) { |
| 699 | 0 | obj = schema.applyGetters(obj, this); |
| 700 | } | |
| 701 | ||
| 702 | 0 | return obj; |
| 703 | }; | |
| 704 | ||
| 705 | /** | |
| 706 | * Returns the schematype for the given `path`. | |
| 707 | * | |
| 708 | * @param {String} path | |
| 709 | * @api private | |
| 710 | * @method $__path | |
| 711 | * @memberOf Document | |
| 712 | */ | |
| 713 | ||
| 714 | 1 | Document.prototype.$__path = function (path) { |
| 715 | 0 | var adhocs = this.$__.adhocPaths |
| 716 | , adhocType = adhocs && adhocs[path]; | |
| 717 | ||
| 718 | 0 | if (adhocType) { |
| 719 | 0 | return adhocType; |
| 720 | } else { | |
| 721 | 0 | return this.schema.path(path); |
| 722 | } | |
| 723 | }; | |
| 724 | ||
| 725 | /** | |
| 726 | * Marks the path as having pending changes to write to the db. | |
| 727 | * | |
| 728 | * _Very helpful when using [Mixed](./schematypes.html#mixed) types._ | |
| 729 | * | |
| 730 | * ####Example: | |
| 731 | * | |
| 732 | * doc.mixed.type = 'changed'; | |
| 733 | * doc.markModified('mixed.type'); | |
| 734 | * doc.save() // changes to mixed.type are now persisted | |
| 735 | * | |
| 736 | * @param {String} path the path to mark modified | |
| 737 | * @api public | |
| 738 | */ | |
| 739 | ||
| 740 | 1 | Document.prototype.markModified = function (path) { |
| 741 | 0 | this.$__.activePaths.modify(path); |
| 742 | } | |
| 743 | ||
| 744 | /** | |
| 745 | * Catches errors that occur during execution of `fn` and stores them to later be passed when `save()` is executed. | |
| 746 | * | |
| 747 | * @param {Function} fn function to execute | |
| 748 | * @param {Object} scope the scope with which to call fn | |
| 749 | * @api private | |
| 750 | * @method $__try | |
| 751 | * @memberOf Document | |
| 752 | */ | |
| 753 | ||
| 754 | 1 | Document.prototype.$__try = function (fn, scope) { |
| 755 | 0 | var res; |
| 756 | 0 | try { |
| 757 | 0 | fn.call(scope); |
| 758 | 0 | res = true; |
| 759 | } catch (e) { | |
| 760 | 0 | this.$__error(e); |
| 761 | 0 | res = false; |
| 762 | } | |
| 763 | 0 | return res; |
| 764 | }; | |
| 765 | ||
| 766 | /** | |
| 767 | * Returns the list of paths that have been modified. | |
| 768 | * | |
| 769 | * @return {Array} | |
| 770 | * @api public | |
| 771 | */ | |
| 772 | ||
| 773 | 1 | Document.prototype.modifiedPaths = function () { |
| 774 | 0 | var directModifiedPaths = Object.keys(this.$__.activePaths.states.modify); |
| 775 | ||
| 776 | 0 | return directModifiedPaths.reduce(function (list, path) { |
| 777 | 0 | var parts = path.split('.'); |
| 778 | 0 | return list.concat(parts.reduce(function (chains, part, i) { |
| 779 | 0 | return chains.concat(parts.slice(0, i).concat(part).join('.')); |
| 780 | }, [])); | |
| 781 | }, []); | |
| 782 | }; | |
| 783 | ||
| 784 | /** | |
| 785 | * Returns true if this document was modified, else false. | |
| 786 | * | |
| 787 | * If `path` is given, checks if a path or any full path containing `path` as part of its path chain has been modified. | |
| 788 | * | |
| 789 | * ####Example | |
| 790 | * | |
| 791 | * doc.set('documents.0.title', 'changed'); | |
| 792 | * doc.isModified() // true | |
| 793 | * doc.isModified('documents') // true | |
| 794 | * doc.isModified('documents.0.title') // true | |
| 795 | * doc.isDirectModified('documents') // false | |
| 796 | * | |
| 797 | * @param {String} [path] optional | |
| 798 | * @return {Boolean} | |
| 799 | * @api public | |
| 800 | */ | |
| 801 | ||
| 802 | 1 | Document.prototype.isModified = function (path) { |
| 803 | 0 | return path |
| 804 | ? !!~this.modifiedPaths().indexOf(path) | |
| 805 | : this.$__.activePaths.some('modify'); | |
| 806 | }; | |
| 807 | ||
| 808 | /** | |
| 809 | * Returns true if `path` was directly set and modified, else false. | |
| 810 | * | |
| 811 | * ####Example | |
| 812 | * | |
| 813 | * doc.set('documents.0.title', 'changed'); | |
| 814 | * doc.isDirectModified('documents.0.title') // true | |
| 815 | * doc.isDirectModified('documents') // false | |
| 816 | * | |
| 817 | * @param {String} path | |
| 818 | * @return {Boolean} | |
| 819 | * @api public | |
| 820 | */ | |
| 821 | ||
| 822 | 1 | Document.prototype.isDirectModified = function (path) { |
| 823 | 0 | return (path in this.$__.activePaths.states.modify); |
| 824 | }; | |
| 825 | ||
| 826 | /** | |
| 827 | * Checks if `path` was initialized. | |
| 828 | * | |
| 829 | * @param {String} path | |
| 830 | * @return {Boolean} | |
| 831 | * @api public | |
| 832 | */ | |
| 833 | ||
| 834 | 1 | Document.prototype.isInit = function (path) { |
| 835 | 0 | return (path in this.$__.activePaths.states.init); |
| 836 | }; | |
| 837 | ||
| 838 | /** | |
| 839 | * Checks if `path` was selected in the source query which initialized this document. | |
| 840 | * | |
| 841 | * ####Example | |
| 842 | * | |
| 843 | * Thing.findOne().select('name').exec(function (err, doc) { | |
| 844 | * doc.isSelected('name') // true | |
| 845 | * doc.isSelected('age') // false | |
| 846 | * }) | |
| 847 | * | |
| 848 | * @param {String} path | |
| 849 | * @return {Boolean} | |
| 850 | * @api public | |
| 851 | */ | |
| 852 | ||
| 853 | 1 | Document.prototype.isSelected = function isSelected (path) { |
| 854 | 0 | if (this.$__.selected) { |
| 855 | ||
| 856 | 0 | if ('_id' === path) { |
| 857 | 0 | return 0 !== this.$__.selected._id; |
| 858 | } | |
| 859 | ||
| 860 | 0 | var paths = Object.keys(this.$__.selected) |
| 861 | , i = paths.length | |
| 862 | , inclusive = false | |
| 863 | , cur | |
| 864 | ||
| 865 | 0 | if (1 === i && '_id' === paths[0]) { |
| 866 | // only _id was selected. | |
| 867 | 0 | return 0 === this.$__.selected._id; |
| 868 | } | |
| 869 | ||
| 870 | 0 | while (i--) { |
| 871 | 0 | cur = paths[i]; |
| 872 | 0 | if ('_id' == cur) continue; |
| 873 | 0 | inclusive = !! this.$__.selected[cur]; |
| 874 | 0 | break; |
| 875 | } | |
| 876 | ||
| 877 | 0 | if (path in this.$__.selected) { |
| 878 | 0 | return inclusive; |
| 879 | } | |
| 880 | ||
| 881 | 0 | i = paths.length; |
| 882 | 0 | var pathDot = path + '.'; |
| 883 | ||
| 884 | 0 | while (i--) { |
| 885 | 0 | cur = paths[i]; |
| 886 | 0 | if ('_id' == cur) continue; |
| 887 | ||
| 888 | 0 | if (0 === cur.indexOf(pathDot)) { |
| 889 | 0 | return inclusive; |
| 890 | } | |
| 891 | ||
| 892 | 0 | if (0 === pathDot.indexOf(cur + '.')) { |
| 893 | 0 | return inclusive; |
| 894 | } | |
| 895 | } | |
| 896 | ||
| 897 | 0 | return ! inclusive; |
| 898 | } | |
| 899 | ||
| 900 | 0 | return true; |
| 901 | } | |
| 902 | ||
| 903 | /** | |
| 904 | * Executes registered validation rules for this document. | |
| 905 | * | |
| 906 | * ####Note: | |
| 907 | * | |
| 908 | * This method is called `pre` save and if a validation rule is violated, [save](#model_Model-save) is aborted and the error is returned to your `callback`. | |
| 909 | * | |
| 910 | * ####Example: | |
| 911 | * | |
| 912 | * doc.validate(function (err) { | |
| 913 | * if (err) handleError(err); | |
| 914 | * else // validation passed | |
| 915 | * }); | |
| 916 | * | |
| 917 | * @param {Function} cb called after validation completes, passing an error if one occurred | |
| 918 | * @api public | |
| 919 | */ | |
| 920 | ||
| 921 | 1 | Document.prototype.validate = function (cb) { |
| 922 | 0 | var self = this |
| 923 | ||
| 924 | // only validate required fields when necessary | |
| 925 | 0 | var paths = Object.keys(this.$__.activePaths.states.require).filter(function (path) { |
| 926 | 0 | if (!self.isSelected(path) && !self.isModified(path)) return false; |
| 927 | 0 | return true; |
| 928 | }); | |
| 929 | ||
| 930 | 0 | paths = paths.concat(Object.keys(this.$__.activePaths.states.init)); |
| 931 | 0 | paths = paths.concat(Object.keys(this.$__.activePaths.states.modify)); |
| 932 | 0 | paths = paths.concat(Object.keys(this.$__.activePaths.states.default)); |
| 933 | ||
| 934 | 0 | if (0 === paths.length) { |
| 935 | 0 | complete(); |
| 936 | 0 | return this; |
| 937 | } | |
| 938 | ||
| 939 | 0 | var validating = {} |
| 940 | , total = 0; | |
| 941 | ||
| 942 | 0 | paths.forEach(validatePath); |
| 943 | 0 | return this; |
| 944 | ||
| 945 | 0 | function validatePath (path) { |
| 946 | 0 | if (validating[path]) return; |
| 947 | ||
| 948 | 0 | validating[path] = true; |
| 949 | 0 | total++; |
| 950 | ||
| 951 | 0 | process.nextTick(function(){ |
| 952 | 0 | var p = self.schema.path(path); |
| 953 | 0 | if (!p) return --total || complete(); |
| 954 | ||
| 955 | 0 | var val = self.getValue(path); |
| 956 | 0 | p.doValidate(val, function (err) { |
| 957 | 0 | if (err) { |
| 958 | 0 | self.invalidate( |
| 959 | path | |
| 960 | , err | |
| 961 | , undefined | |
| 962 | , true // embedded docs | |
| 963 | ); | |
| 964 | } | |
| 965 | 0 | --total || complete(); |
| 966 | }, self); | |
| 967 | }); | |
| 968 | } | |
| 969 | ||
| 970 | 0 | function complete () { |
| 971 | 0 | var err = self.$__.validationError; |
| 972 | 0 | self.$__.validationError = undefined; |
| 973 | 0 | self.emit('validate', self); |
| 974 | 0 | cb(err); |
| 975 | } | |
| 976 | }; | |
| 977 | ||
| 978 | /** | |
| 979 | * Marks a path as invalid, causing validation to fail. | |
| 980 | * | |
| 981 | * The `errorMsg` argument will become the message of the `ValidationError`. | |
| 982 | * | |
| 983 | * The `value` argument (if passed) will be available through the `ValidationError.value` property. | |
| 984 | * | |
| 985 | * doc.invalidate('size', 'must be less than 20', 14); | |
| 986 | ||
| 987 | * doc.validate(function (err) { | |
| 988 | * console.log(err) | |
| 989 | * // prints | |
| 990 | * { message: 'Validation failed', | |
| 991 | * name: 'ValidationError', | |
| 992 | * errors: | |
| 993 | * { size: | |
| 994 | * { message: 'must be less than 20', | |
| 995 | * name: 'ValidatorError', | |
| 996 | * path: 'size', | |
| 997 | * type: 'user defined', | |
| 998 | * value: 14 } } } | |
| 999 | * }) | |
| 1000 | * | |
| 1001 | * @param {String} path the field to invalidate | |
| 1002 | * @param {String|Error} errorMsg the error which states the reason `path` was invalid | |
| 1003 | * @param {Object|String|Number|any} value optional invalid value | |
| 1004 | * @api public | |
| 1005 | */ | |
| 1006 | ||
| 1007 | 1 | Document.prototype.invalidate = function (path, err, val) { |
| 1008 | 0 | if (!this.$__.validationError) { |
| 1009 | 0 | this.$__.validationError = new ValidationError(this); |
| 1010 | } | |
| 1011 | ||
| 1012 | 0 | if (!err || 'string' === typeof err) { |
| 1013 | 0 | err = new ValidatorError(path, err, 'user defined', val) |
| 1014 | } | |
| 1015 | ||
| 1016 | 0 | if (this.$__.validationError == err) return; |
| 1017 | ||
| 1018 | 0 | this.$__.validationError.errors[path] = err; |
| 1019 | } | |
| 1020 | ||
| 1021 | /** | |
| 1022 | * Resets the internal modified state of this document. | |
| 1023 | * | |
| 1024 | * @api private | |
| 1025 | * @return {Document} | |
| 1026 | * @method $__reset | |
| 1027 | * @memberOf Document | |
| 1028 | */ | |
| 1029 | ||
| 1030 | 1 | Document.prototype.$__reset = function reset () { |
| 1031 | 0 | var self = this; |
| 1032 | 0 | DocumentArray || (DocumentArray = require('./types/documentarray')); |
| 1033 | ||
| 1034 | 0 | this.$__.activePaths |
| 1035 | .map('init', 'modify', function (i) { | |
| 1036 | 0 | return self.getValue(i); |
| 1037 | }) | |
| 1038 | .filter(function (val) { | |
| 1039 | 0 | return val && val instanceof DocumentArray && val.length; |
| 1040 | }) | |
| 1041 | .forEach(function (array) { | |
| 1042 | 0 | var i = array.length; |
| 1043 | 0 | while (i--) { |
| 1044 | 0 | var doc = array[i]; |
| 1045 | 0 | if (!doc) continue; |
| 1046 | 0 | doc.$__reset(); |
| 1047 | } | |
| 1048 | }); | |
| 1049 | ||
| 1050 | // clear atomics | |
| 1051 | 0 | this.$__dirty().forEach(function (dirt) { |
| 1052 | 0 | var type = dirt.value; |
| 1053 | 0 | if (type && type._atomics) { |
| 1054 | 0 | type._atomics = {}; |
| 1055 | } | |
| 1056 | }); | |
| 1057 | ||
| 1058 | // Clear 'modify'('dirty') cache | |
| 1059 | 0 | this.$__.activePaths.clear('modify'); |
| 1060 | 0 | this.$__.validationError = undefined; |
| 1061 | 0 | this.errors = undefined; |
| 1062 | 0 | var self = this; |
| 1063 | 0 | this.schema.requiredPaths().forEach(function (path) { |
| 1064 | 0 | self.$__.activePaths.require(path); |
| 1065 | }); | |
| 1066 | ||
| 1067 | 0 | return this; |
| 1068 | } | |
| 1069 | ||
| 1070 | /** | |
| 1071 | * Returns this documents dirty paths / vals. | |
| 1072 | * | |
| 1073 | * @api private | |
| 1074 | * @method $__dirty | |
| 1075 | * @memberOf Document | |
| 1076 | */ | |
| 1077 | ||
| 1078 | 1 | Document.prototype.$__dirty = function () { |
| 1079 | 0 | var self = this; |
| 1080 | ||
| 1081 | 0 | var all = this.$__.activePaths.map('modify', function (path) { |
| 1082 | 0 | return { path: path |
| 1083 | , value: self.getValue(path) | |
| 1084 | , schema: self.$__path(path) }; | |
| 1085 | }); | |
| 1086 | ||
| 1087 | // Sort dirty paths in a flat hierarchy. | |
| 1088 | 0 | all.sort(function (a, b) { |
| 1089 | 0 | return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0)); |
| 1090 | }); | |
| 1091 | ||
| 1092 | // Ignore "foo.a" if "foo" is dirty already. | |
| 1093 | 0 | var minimal = [] |
| 1094 | , lastPath | |
| 1095 | , top; | |
| 1096 | ||
| 1097 | 0 | all.forEach(function (item, i) { |
| 1098 | 0 | if (item.path.indexOf(lastPath) !== 0) { |
| 1099 | 0 | lastPath = item.path + '.'; |
| 1100 | 0 | minimal.push(item); |
| 1101 | 0 | top = item; |
| 1102 | } else { | |
| 1103 | // special case for top level MongooseArrays | |
| 1104 | 0 | if (top.value && top.value._atomics && top.value.hasAtomics()) { |
| 1105 | // the `top` array itself and a sub path of `top` are being modified. | |
| 1106 | // the only way to honor all of both modifications is through a $set | |
| 1107 | // of entire array. | |
| 1108 | 0 | top.value._atomics = {}; |
| 1109 | 0 | top.value._atomics.$set = top.value; |
| 1110 | } | |
| 1111 | } | |
| 1112 | }); | |
| 1113 | ||
| 1114 | 0 | top = lastPath = null; |
| 1115 | 0 | return minimal; |
| 1116 | } | |
| 1117 | ||
| 1118 | /*! | |
| 1119 | * Compiles schemas. | |
| 1120 | */ | |
| 1121 | ||
| 1122 | 1 | function compile (tree, proto, prefix) { |
| 1123 | 0 | var keys = Object.keys(tree) |
| 1124 | , i = keys.length | |
| 1125 | , limb | |
| 1126 | , key; | |
| 1127 | ||
| 1128 | 0 | while (i--) { |
| 1129 | 0 | key = keys[i]; |
| 1130 | 0 | limb = tree[key]; |
| 1131 | ||
| 1132 | 0 | define(key |
| 1133 | , (('Object' === limb.constructor.name | |
| 1134 | && Object.keys(limb).length) | |
| 1135 | && (!limb.type || limb.type.type) | |
| 1136 | ? limb | |
| 1137 | : null) | |
| 1138 | , proto | |
| 1139 | , prefix | |
| 1140 | , keys); | |
| 1141 | } | |
| 1142 | }; | |
| 1143 | ||
| 1144 | /*! | |
| 1145 | * Defines the accessor named prop on the incoming prototype. | |
| 1146 | */ | |
| 1147 | ||
| 1148 | 1 | function define (prop, subprops, prototype, prefix, keys) { |
| 1149 | 0 | var prefix = prefix || '' |
| 1150 | , path = (prefix ? prefix + '.' : '') + prop; | |
| 1151 | ||
| 1152 | 0 | if (subprops) { |
| 1153 | ||
| 1154 | 0 | Object.defineProperty(prototype, prop, { |
| 1155 | enumerable: true | |
| 1156 | , get: function () { | |
| 1157 | 0 | if (!this.$__.getters) |
| 1158 | 0 | this.$__.getters = {}; |
| 1159 | ||
| 1160 | 0 | if (!this.$__.getters[path]) { |
| 1161 | 0 | var nested = Object.create(this); |
| 1162 | ||
| 1163 | // save scope for nested getters/setters | |
| 1164 | 0 | if (!prefix) nested.$__.scope = this; |
| 1165 | ||
| 1166 | // shadow inherited getters from sub-objects so | |
| 1167 | // thing.nested.nested.nested... doesn't occur (gh-366) | |
| 1168 | 0 | var i = 0 |
| 1169 | , len = keys.length; | |
| 1170 | ||
| 1171 | 0 | for (; i < len; ++i) { |
| 1172 | // over-write the parents getter without triggering it | |
| 1173 | 0 | Object.defineProperty(nested, keys[i], { |
| 1174 | enumerable: false // It doesn't show up. | |
| 1175 | , writable: true // We can set it later. | |
| 1176 | , configurable: true // We can Object.defineProperty again. | |
| 1177 | , value: undefined // It shadows its parent. | |
| 1178 | }); | |
| 1179 | } | |
| 1180 | ||
| 1181 | 0 | nested.toObject = function () { |
| 1182 | 0 | return this.get(path); |
| 1183 | }; | |
| 1184 | ||
| 1185 | 0 | compile(subprops, nested, path); |
| 1186 | 0 | this.$__.getters[path] = nested; |
| 1187 | } | |
| 1188 | ||
| 1189 | 0 | return this.$__.getters[path]; |
| 1190 | } | |
| 1191 | , set: function (v) { | |
| 1192 | 0 | if (v instanceof Document) v = v.toObject(); |
| 1193 | 0 | return (this.$__.scope || this).set(path, v); |
| 1194 | } | |
| 1195 | }); | |
| 1196 | ||
| 1197 | } else { | |
| 1198 | ||
| 1199 | 0 | Object.defineProperty(prototype, prop, { |
| 1200 | enumerable: true | |
| 1201 | 0 | , get: function ( ) { return this.get.call(this.$__.scope || this, path); } |
| 1202 | 0 | , set: function (v) { return this.set.call(this.$__.scope || this, path, v); } |
| 1203 | }); | |
| 1204 | } | |
| 1205 | }; | |
| 1206 | ||
| 1207 | /** | |
| 1208 | * Assigns/compiles `schema` into this documents prototype. | |
| 1209 | * | |
| 1210 | * @param {Schema} schema | |
| 1211 | * @api private | |
| 1212 | * @method $__setSchema | |
| 1213 | * @memberOf Document | |
| 1214 | */ | |
| 1215 | ||
| 1216 | 1 | Document.prototype.$__setSchema = function (schema) { |
| 1217 | 0 | compile(schema.tree, this); |
| 1218 | 0 | this.schema = schema; |
| 1219 | } | |
| 1220 | ||
| 1221 | /** | |
| 1222 | * Register default hooks | |
| 1223 | * | |
| 1224 | * @api private | |
| 1225 | * @method $__registerHooks | |
| 1226 | * @memberOf Document | |
| 1227 | */ | |
| 1228 | ||
| 1229 | 1 | Document.prototype.$__registerHooks = function () { |
| 1230 | 0 | if (!this.save) return; |
| 1231 | ||
| 1232 | 0 | DocumentArray || (DocumentArray = require('./types/documentarray')); |
| 1233 | ||
| 1234 | 0 | this.pre('save', function (next) { |
| 1235 | // validate all document arrays. | |
| 1236 | // we keep the error semaphore to make sure we don't | |
| 1237 | // call `save` unnecessarily (we only need 1 error) | |
| 1238 | 0 | var subdocs = 0 |
| 1239 | , error = false | |
| 1240 | , self = this; | |
| 1241 | ||
| 1242 | // check for DocumentArrays | |
| 1243 | 0 | var arrays = this.$__.activePaths |
| 1244 | .map('init', 'modify', function (i) { | |
| 1245 | 0 | return self.getValue(i); |
| 1246 | }) | |
| 1247 | .filter(function (val) { | |
| 1248 | 0 | return val && val instanceof DocumentArray && val.length; |
| 1249 | }); | |
| 1250 | ||
| 1251 | 0 | if (!arrays.length) |
| 1252 | 0 | return next(); |
| 1253 | ||
| 1254 | 0 | arrays.forEach(function (array) { |
| 1255 | 0 | if (error) return; |
| 1256 | ||
| 1257 | // handle sparse arrays by using for loop vs array.forEach | |
| 1258 | // which skips the sparse elements | |
| 1259 | ||
| 1260 | 0 | var len = array.length |
| 1261 | 0 | subdocs += len; |
| 1262 | ||
| 1263 | 0 | for (var i = 0; i < len; ++i) { |
| 1264 | 0 | if (error) break; |
| 1265 | ||
| 1266 | 0 | var doc = array[i]; |
| 1267 | 0 | if (!doc) { |
| 1268 | 0 | --subdocs || next(); |
| 1269 | 0 | continue; |
| 1270 | } | |
| 1271 | ||
| 1272 | 0 | doc.save(handleSave); |
| 1273 | } | |
| 1274 | }); | |
| 1275 | ||
| 1276 | 0 | function handleSave (err) { |
| 1277 | 0 | if (error) return; |
| 1278 | ||
| 1279 | 0 | if (err) { |
| 1280 | 0 | self.$__.validationError = undefined; |
| 1281 | 0 | return next(error = err); |
| 1282 | } | |
| 1283 | ||
| 1284 | 0 | --subdocs || next(); |
| 1285 | } | |
| 1286 | ||
| 1287 | }, function (err) { | |
| 1288 | // emit on the Model if listening | |
| 1289 | 0 | if (this.constructor.listeners('error').length) { |
| 1290 | 0 | this.constructor.emit('error', err); |
| 1291 | } else { | |
| 1292 | // emit on the connection | |
| 1293 | 0 | if (!this.db.listeners('error').length) { |
| 1294 | 0 | err.stack = 'No listeners detected, throwing. ' |
| 1295 | + 'Consider adding an error listener to your connection.\n' | |
| 1296 | + err.stack | |
| 1297 | } | |
| 1298 | 0 | this.db.emit('error', err); |
| 1299 | } | |
| 1300 | }).pre('save', function checkForExistingErrors (next) { | |
| 1301 | // if any doc.set() calls failed | |
| 1302 | 0 | var err = this.$__.saveError; |
| 1303 | 0 | if (err) { |
| 1304 | 0 | this.$__.saveError = null; |
| 1305 | 0 | next(err); |
| 1306 | } else { | |
| 1307 | 0 | next(); |
| 1308 | } | |
| 1309 | }).pre('save', function validation (next) { | |
| 1310 | 0 | return this.validate(next); |
| 1311 | }); | |
| 1312 | ||
| 1313 | // add user defined queues | |
| 1314 | 0 | this.$__doQueue(); |
| 1315 | }; | |
| 1316 | ||
| 1317 | /** | |
| 1318 | * Registers an error | |
| 1319 | * | |
| 1320 | * @param {Error} err | |
| 1321 | * @api private | |
| 1322 | * @method $__error | |
| 1323 | * @memberOf Document | |
| 1324 | */ | |
| 1325 | ||
| 1326 | 1 | Document.prototype.$__error = function (err) { |
| 1327 | 0 | this.$__.saveError = err; |
| 1328 | 0 | return this; |
| 1329 | }; | |
| 1330 | ||
| 1331 | /** | |
| 1332 | * Executes methods queued from the Schema definition | |
| 1333 | * | |
| 1334 | * @api private | |
| 1335 | * @method $__doQueue | |
| 1336 | * @memberOf Document | |
| 1337 | */ | |
| 1338 | ||
| 1339 | 1 | Document.prototype.$__doQueue = function () { |
| 1340 | 0 | var q = this.schema && this.schema.callQueue; |
| 1341 | 0 | if (q) { |
| 1342 | 0 | for (var i = 0, l = q.length; i < l; i++) { |
| 1343 | 0 | this[q[i][0]].apply(this, q[i][1]); |
| 1344 | } | |
| 1345 | } | |
| 1346 | 0 | return this; |
| 1347 | }; | |
| 1348 | ||
| 1349 | /** | |
| 1350 | * Converts this document into a plain javascript object, ready for storage in MongoDB. | |
| 1351 | * | |
| 1352 | * Buffers are converted to instances of [mongodb.Binary](http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html) for proper storage. | |
| 1353 | * | |
| 1354 | * ####Options: | |
| 1355 | * | |
| 1356 | * - `getters` apply all getters (path and virtual getters) | |
| 1357 | * - `virtuals` apply virtual getters (can override `getters` option) | |
| 1358 | * - `minimize` remove empty objects (defaults to true) | |
| 1359 | * - `transform` a transform function to apply to the resulting document before returning | |
| 1360 | * | |
| 1361 | * ####Getters/Virtuals | |
| 1362 | * | |
| 1363 | * Example of only applying path getters | |
| 1364 | * | |
| 1365 | * doc.toObject({ getters: true, virtuals: false }) | |
| 1366 | * | |
| 1367 | * Example of only applying virtual getters | |
| 1368 | * | |
| 1369 | * doc.toObject({ virtuals: true }) | |
| 1370 | * | |
| 1371 | * Example of applying both path and virtual getters | |
| 1372 | * | |
| 1373 | * doc.toObject({ getters: true }) | |
| 1374 | * | |
| 1375 | * To apply these options to every document of your schema by default, set your [schemas](#schema_Schema) `toObject` option to the same argument. | |
| 1376 | * | |
| 1377 | * schema.set('toObject', { virtuals: true }) | |
| 1378 | * | |
| 1379 | * ####Transform | |
| 1380 | * | |
| 1381 | * We may need to perform a transformation of the resulting object based on some criteria, say to remove some sensitive information or return a custom object. In this case we set the optional `transform` function. | |
| 1382 | * | |
| 1383 | * Transform functions receive three arguments | |
| 1384 | * | |
| 1385 | * function (doc, ret, options) {} | |
| 1386 | * | |
| 1387 | * - `doc` The mongoose document which is being converted | |
| 1388 | * - `ret` The plain object representation which has been converted | |
| 1389 | * - `options` The options in use (either schema options or the options passed inline) | |
| 1390 | * | |
| 1391 | * ####Example | |
| 1392 | * | |
| 1393 | * // specify the transform schema option | |
| 1394 | * if (!schema.options.toObject) schema.options.toObject = {}; | |
| 1395 | * schema.options.toObject.transform = function (doc, ret, options) { | |
| 1396 | * // remove the _id of every document before returning the result | |
| 1397 | * delete ret._id; | |
| 1398 | * } | |
| 1399 | * | |
| 1400 | * // without the transformation in the schema | |
| 1401 | * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' } | |
| 1402 | * | |
| 1403 | * // with the transformation | |
| 1404 | * doc.toObject(); // { name: 'Wreck-it Ralph' } | |
| 1405 | * | |
| 1406 | * With transformations we can do a lot more than remove properties. We can even return completely new customized objects: | |
| 1407 | * | |
| 1408 | * if (!schema.options.toObject) schema.options.toObject = {}; | |
| 1409 | * schema.options.toObject.transform = function (doc, ret, options) { | |
| 1410 | * return { movie: ret.name } | |
| 1411 | * } | |
| 1412 | * | |
| 1413 | * // without the transformation in the schema | |
| 1414 | * doc.toObject(); // { _id: 'anId', name: 'Wreck-it Ralph' } | |
| 1415 | * | |
| 1416 | * // with the transformation | |
| 1417 | * doc.toObject(); // { movie: 'Wreck-it Ralph' } | |
| 1418 | * | |
| 1419 | * _Note: if a transform function returns `undefined`, the return value will be ignored._ | |
| 1420 | * | |
| 1421 | * Transformations may also be applied inline, overridding any transform set in the options: | |
| 1422 | * | |
| 1423 | * function xform (doc, ret, options) { | |
| 1424 | * return { inline: ret.name, custom: true } | |
| 1425 | * } | |
| 1426 | * | |
| 1427 | * // pass the transform as an inline option | |
| 1428 | * doc.toObject({ transform: xform }); // { inline: 'Wreck-it Ralph', custom: true } | |
| 1429 | * | |
| 1430 | * _Note: if you call `toObject` and pass any options, the transform declared in your schema options will __not__ be applied. To force its application pass `transform: true`_ | |
| 1431 | * | |
| 1432 | * if (!schema.options.toObject) schema.options.toObject = {}; | |
| 1433 | * schema.options.toObject.hide = '_id'; | |
| 1434 | * schema.options.toObject.transform = function (doc, ret, options) { | |
| 1435 | * if (options.hide) { | |
| 1436 | * options.hide.split(' ').forEach(function (prop) { | |
| 1437 | * delete ret[prop]; | |
| 1438 | * }); | |
| 1439 | * } | |
| 1440 | * } | |
| 1441 | * | |
| 1442 | * var doc = new Doc({ _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }); | |
| 1443 | * doc.toObject(); // { secret: 47, name: 'Wreck-it Ralph' } | |
| 1444 | * doc.toObject({ hide: 'secret _id' }); // { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' } | |
| 1445 | * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' } | |
| 1446 | * | |
| 1447 | * Transforms are applied to the document _and each of its sub-documents_. To determine whether or not you are currently operating on a sub-document you might use the following guard: | |
| 1448 | * | |
| 1449 | * if ('function' == typeof doc.ownerDocument) { | |
| 1450 | * // working with a sub doc | |
| 1451 | * } | |
| 1452 | * | |
| 1453 | * Transforms, like all of these options, are also available for `toJSON`. | |
| 1454 | * | |
| 1455 | * See [schema options](/docs/guide.html#toObject) for some more details. | |
| 1456 | * | |
| 1457 | * _During save, no custom options are applied to the document before being sent to the database._ | |
| 1458 | * | |
| 1459 | * @param {Object} [options] | |
| 1460 | * @return {Object} js object | |
| 1461 | * @see mongodb.Binary http://mongodb.github.com/node-mongodb-native/api-bson-generated/binary.html | |
| 1462 | * @api public | |
| 1463 | */ | |
| 1464 | ||
| 1465 | 1 | Document.prototype.toObject = function (options) { |
| 1466 | 0 | if (options && options.depopulate && this.$__.wasPopulated) { |
| 1467 | // populated paths that we set to a document | |
| 1468 | 0 | return clone(this._id, options); |
| 1469 | } | |
| 1470 | ||
| 1471 | // When internally saving this document we always pass options, | |
| 1472 | // bypassing the custom schema options. | |
| 1473 | 0 | if (!(options && 'Object' == options.constructor.name)) { |
| 1474 | 0 | options = this.schema.options.toObject |
| 1475 | ? clone(this.schema.options.toObject) | |
| 1476 | : {}; | |
| 1477 | } | |
| 1478 | ||
| 1479 | 0 | ;('minimize' in options) || (options.minimize = this.schema.options.minimize); |
| 1480 | ||
| 1481 | 0 | var ret = clone(this._doc, options); |
| 1482 | ||
| 1483 | 0 | if (options.virtuals || options.getters && false !== options.virtuals) { |
| 1484 | 0 | applyGetters(this, ret, 'virtuals', options); |
| 1485 | } | |
| 1486 | ||
| 1487 | 0 | if (options.getters) { |
| 1488 | 0 | applyGetters(this, ret, 'paths', options); |
| 1489 | // applyGetters for paths will add nested empty objects; | |
| 1490 | // if minimize is set, we need to remove them. | |
| 1491 | 0 | if (options.minimize) { |
| 1492 | 0 | ret = minimize(ret) || {}; |
| 1493 | } | |
| 1494 | } | |
| 1495 | ||
| 1496 | // In the case where a subdocument has its own transform function, we need to | |
| 1497 | // check and see if the parent has a transform (options.transform) and if the | |
| 1498 | // child schema has a transform (this.schema.options.toObject) In this case, | |
| 1499 | // we need to adjust options.transform to be the child schema's transform and | |
| 1500 | // not the parent schema's | |
| 1501 | 0 | if (true === options.transform || |
| 1502 | (this.schema.options.toObject && options.transform)) { | |
| 1503 | 0 | var opts = options.json |
| 1504 | ? this.schema.options.toJSON | |
| 1505 | : this.schema.options.toObject; | |
| 1506 | 0 | if (opts) { |
| 1507 | 0 | options.transform = opts.transform; |
| 1508 | } | |
| 1509 | } | |
| 1510 | ||
| 1511 | 0 | if ('function' == typeof options.transform) { |
| 1512 | 0 | var xformed = options.transform(this, ret, options); |
| 1513 | 0 | if ('undefined' != typeof xformed) ret = xformed; |
| 1514 | } | |
| 1515 | ||
| 1516 | 0 | return ret; |
| 1517 | }; | |
| 1518 | ||
| 1519 | /*! | |
| 1520 | * Minimizes an object, removing undefined values and empty objects | |
| 1521 | * | |
| 1522 | * @param {Object} object to minimize | |
| 1523 | * @return {Object} | |
| 1524 | */ | |
| 1525 | ||
| 1526 | 1 | function minimize (obj) { |
| 1527 | 0 | var keys = Object.keys(obj) |
| 1528 | , i = keys.length | |
| 1529 | , hasKeys | |
| 1530 | , key | |
| 1531 | , val | |
| 1532 | ||
| 1533 | 0 | while (i--) { |
| 1534 | 0 | key = keys[i]; |
| 1535 | 0 | val = obj[key]; |
| 1536 | ||
| 1537 | 0 | if (utils.isObject(val)) { |
| 1538 | 0 | obj[key] = minimize(val); |
| 1539 | } | |
| 1540 | ||
| 1541 | 0 | if (undefined === obj[key]) { |
| 1542 | 0 | delete obj[key]; |
| 1543 | 0 | continue; |
| 1544 | } | |
| 1545 | ||
| 1546 | 0 | hasKeys = true; |
| 1547 | } | |
| 1548 | ||
| 1549 | 0 | return hasKeys |
| 1550 | ? obj | |
| 1551 | : undefined; | |
| 1552 | } | |
| 1553 | ||
| 1554 | /*! | |
| 1555 | * Applies virtuals properties to `json`. | |
| 1556 | * | |
| 1557 | * @param {Document} self | |
| 1558 | * @param {Object} json | |
| 1559 | * @param {String} type either `virtuals` or `paths` | |
| 1560 | * @return {Object} `json` | |
| 1561 | */ | |
| 1562 | ||
| 1563 | 1 | function applyGetters (self, json, type, options) { |
| 1564 | 0 | var schema = self.schema |
| 1565 | , paths = Object.keys(schema[type]) | |
| 1566 | , i = paths.length | |
| 1567 | , path | |
| 1568 | ||
| 1569 | 0 | while (i--) { |
| 1570 | 0 | path = paths[i]; |
| 1571 | ||
| 1572 | 0 | var parts = path.split('.') |
| 1573 | , plen = parts.length | |
| 1574 | , last = plen - 1 | |
| 1575 | , branch = json | |
| 1576 | , part | |
| 1577 | ||
| 1578 | 0 | for (var ii = 0; ii < plen; ++ii) { |
| 1579 | 0 | part = parts[ii]; |
| 1580 | 0 | if (ii === last) { |
| 1581 | 0 | branch[part] = clone(self.get(path), options); |
| 1582 | } else { | |
| 1583 | 0 | branch = branch[part] || (branch[part] = {}); |
| 1584 | } | |
| 1585 | } | |
| 1586 | } | |
| 1587 | ||
| 1588 | 0 | return json; |
| 1589 | } | |
| 1590 | ||
| 1591 | /** | |
| 1592 | * The return value of this method is used in calls to JSON.stringify(doc). | |
| 1593 | * | |
| 1594 | * This method accepts the same options as [Document#toObject](#document_Document-toObject). To apply the options to every document of your schema by default, set your [schemas](#schema_Schema) `toJSON` option to the same argument. | |
| 1595 | * | |
| 1596 | * schema.set('toJSON', { virtuals: true }) | |
| 1597 | * | |
| 1598 | * See [schema options](/docs/guide.html#toJSON) for details. | |
| 1599 | * | |
| 1600 | * @param {Object} options | |
| 1601 | * @return {Object} | |
| 1602 | * @see Document#toObject #document_Document-toObject | |
| 1603 | * @api public | |
| 1604 | */ | |
| 1605 | ||
| 1606 | 1 | Document.prototype.toJSON = function (options) { |
| 1607 | // check for object type since an array of documents | |
| 1608 | // being stringified passes array indexes instead | |
| 1609 | // of options objects. JSON.stringify([doc, doc]) | |
| 1610 | // The second check here is to make sure that populated documents (or | |
| 1611 | // subdocuments) use their own options for `.toJSON()` instead of their | |
| 1612 | // parent's | |
| 1613 | 0 | if (!(options && 'Object' == options.constructor.name) |
| 1614 | || ((!options || options.json) && this.schema.options.toJSON)) { | |
| 1615 | 0 | options = this.schema.options.toJSON |
| 1616 | ? clone(this.schema.options.toJSON) | |
| 1617 | : {}; | |
| 1618 | } | |
| 1619 | 0 | options.json = true; |
| 1620 | ||
| 1621 | 0 | return this.toObject(options); |
| 1622 | }; | |
| 1623 | ||
| 1624 | /** | |
| 1625 | * Helper for console.log | |
| 1626 | * | |
| 1627 | * @api public | |
| 1628 | */ | |
| 1629 | ||
| 1630 | 1 | Document.prototype.inspect = function (options) { |
| 1631 | 0 | var opts = options && 'Object' == options.constructor.name ? options : |
| 1632 | this.schema.options.toObject ? clone(this.schema.options.toObject) : | |
| 1633 | {}; | |
| 1634 | 0 | opts.minimize = false; |
| 1635 | 0 | return inspect(this.toObject(opts)); |
| 1636 | }; | |
| 1637 | ||
| 1638 | /** | |
| 1639 | * Helper for console.log | |
| 1640 | * | |
| 1641 | * @api public | |
| 1642 | * @method toString | |
| 1643 | */ | |
| 1644 | ||
| 1645 | 1 | Document.prototype.toString = Document.prototype.inspect; |
| 1646 | ||
| 1647 | /** | |
| 1648 | * Returns true if the Document stores the same data as doc. | |
| 1649 | * | |
| 1650 | * Documents are considered equal when they have matching `_id`s. | |
| 1651 | * | |
| 1652 | * @param {Document} doc a document to compare | |
| 1653 | * @return {Boolean} | |
| 1654 | * @api public | |
| 1655 | */ | |
| 1656 | ||
| 1657 | 1 | Document.prototype.equals = function (doc) { |
| 1658 | 0 | var tid = this.get('_id'); |
| 1659 | 0 | var docid = doc.get('_id'); |
| 1660 | 0 | return tid && tid.equals |
| 1661 | ? tid.equals(docid) | |
| 1662 | : tid === docid; | |
| 1663 | } | |
| 1664 | ||
| 1665 | /** | |
| 1666 | * Populates document references, executing the `callback` when complete. | |
| 1667 | * | |
| 1668 | * ####Example: | |
| 1669 | * | |
| 1670 | * doc | |
| 1671 | * .populate('company') | |
| 1672 | * .populate({ | |
| 1673 | * path: 'notes', | |
| 1674 | * match: /airline/, | |
| 1675 | * select: 'text', | |
| 1676 | * model: 'modelName' | |
| 1677 | * options: opts | |
| 1678 | * }, function (err, user) { | |
| 1679 | * assert(doc._id == user._id) // the document itself is passed | |
| 1680 | * }) | |
| 1681 | * | |
| 1682 | * // summary | |
| 1683 | * doc.populate(path) // not executed | |
| 1684 | * doc.populate(options); // not executed | |
| 1685 | * doc.populate(path, callback) // executed | |
| 1686 | * doc.populate(options, callback); // executed | |
| 1687 | * doc.populate(callback); // executed | |
| 1688 | * | |
| 1689 | * | |
| 1690 | * ####NOTE: | |
| 1691 | * | |
| 1692 | * Population does not occur unless a `callback` is passed. | |
| 1693 | * Passing the same path a second time will overwrite the previous path options. | |
| 1694 | * See [Model.populate()](#model_Model.populate) for explaination of options. | |
| 1695 | * | |
| 1696 | * @see Model.populate #model_Model.populate | |
| 1697 | * @param {String|Object} [path] The path to populate or an options object | |
| 1698 | * @param {Function} [callback] When passed, population is invoked | |
| 1699 | * @api public | |
| 1700 | * @return {Document} this | |
| 1701 | */ | |
| 1702 | ||
| 1703 | 1 | Document.prototype.populate = function populate () { |
| 1704 | 0 | if (0 === arguments.length) return this; |
| 1705 | ||
| 1706 | 0 | var pop = this.$__.populate || (this.$__.populate = {}); |
| 1707 | 0 | var args = utils.args(arguments); |
| 1708 | 0 | var fn; |
| 1709 | ||
| 1710 | 0 | if ('function' == typeof args[args.length-1]) { |
| 1711 | 0 | fn = args.pop(); |
| 1712 | } | |
| 1713 | ||
| 1714 | // allow `doc.populate(callback)` | |
| 1715 | 0 | if (args.length) { |
| 1716 | // use hash to remove duplicate paths | |
| 1717 | 0 | var res = utils.populate.apply(null, args); |
| 1718 | 0 | for (var i = 0; i < res.length; ++i) { |
| 1719 | 0 | pop[res[i].path] = res[i]; |
| 1720 | } | |
| 1721 | } | |
| 1722 | ||
| 1723 | 0 | if (fn) { |
| 1724 | 0 | var paths = utils.object.vals(pop); |
| 1725 | 0 | this.$__.populate = undefined; |
| 1726 | 0 | this.constructor.populate(this, paths, fn); |
| 1727 | } | |
| 1728 | ||
| 1729 | 0 | return this; |
| 1730 | } | |
| 1731 | ||
| 1732 | /** | |
| 1733 | * Gets _id(s) used during population of the given `path`. | |
| 1734 | * | |
| 1735 | * ####Example: | |
| 1736 | * | |
| 1737 | * Model.findOne().populate('author').exec(function (err, doc) { | |
| 1738 | * console.log(doc.author.name) // Dr.Seuss | |
| 1739 | * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7' | |
| 1740 | * }) | |
| 1741 | * | |
| 1742 | * If the path was not populated, undefined is returned. | |
| 1743 | * | |
| 1744 | * @param {String} path | |
| 1745 | * @return {Array|ObjectId|Number|Buffer|String|undefined} | |
| 1746 | * @api public | |
| 1747 | */ | |
| 1748 | ||
| 1749 | 1 | Document.prototype.populated = function (path, val, options) { |
| 1750 | // val and options are internal | |
| 1751 | ||
| 1752 | 0 | if (null == val) { |
| 1753 | 0 | if (!this.$__.populated) return undefined; |
| 1754 | 0 | var v = this.$__.populated[path]; |
| 1755 | 0 | if (v) return v.value; |
| 1756 | 0 | return undefined; |
| 1757 | } | |
| 1758 | ||
| 1759 | // internal | |
| 1760 | ||
| 1761 | 0 | if (true === val) { |
| 1762 | 0 | if (!this.$__.populated) return undefined; |
| 1763 | 0 | return this.$__.populated[path]; |
| 1764 | } | |
| 1765 | ||
| 1766 | 0 | this.$__.populated || (this.$__.populated = {}); |
| 1767 | 0 | this.$__.populated[path] = { value: val, options: options }; |
| 1768 | 0 | return val; |
| 1769 | } | |
| 1770 | ||
| 1771 | /** | |
| 1772 | * Returns the full path to this document. | |
| 1773 | * | |
| 1774 | * @param {String} [path] | |
| 1775 | * @return {String} | |
| 1776 | * @api private | |
| 1777 | * @method $__fullPath | |
| 1778 | * @memberOf Document | |
| 1779 | */ | |
| 1780 | ||
| 1781 | 1 | Document.prototype.$__fullPath = function (path) { |
| 1782 | // overridden in SubDocuments | |
| 1783 | 0 | return path || ''; |
| 1784 | } | |
| 1785 | ||
| 1786 | /*! | |
| 1787 | * Module exports. | |
| 1788 | */ | |
| 1789 | ||
| 1790 | 1 | Document.ValidationError = ValidationError; |
| 1791 | 1 | module.exports = exports = Document; |
| 1792 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Binary = require('mongodb').BSONPure.Binary; |
| 7 | ||
| 8 | 1 | module.exports = exports = Binary; |
| 9 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseCollection = require('../../collection') |
| 7 | , Collection = require('mongodb').Collection | |
| 8 | , STATES = require('../../connectionstate') | |
| 9 | , utils = require('../../utils') | |
| 10 | ||
| 11 | /** | |
| 12 | * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) collection implementation. | |
| 13 | * | |
| 14 | * All methods methods from the [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver are copied and wrapped in queue management. | |
| 15 | * | |
| 16 | * @inherits Collection | |
| 17 | * @api private | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | function NativeCollection () { |
| 21 | 0 | this.collection = null; |
| 22 | 0 | MongooseCollection.apply(this, arguments); |
| 23 | } | |
| 24 | ||
| 25 | /*! | |
| 26 | * Inherit from abstract Collection. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | NativeCollection.prototype.__proto__ = MongooseCollection.prototype; |
| 30 | ||
| 31 | /** | |
| 32 | * Called when the connection opens. | |
| 33 | * | |
| 34 | * @api private | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | NativeCollection.prototype.onOpen = function () { |
| 38 | 0 | var self = this; |
| 39 | ||
| 40 | // always get a new collection in case the user changed host:port | |
| 41 | // of parent db instance when re-opening the connection. | |
| 42 | ||
| 43 | 0 | if (!self.opts.capped.size) { |
| 44 | // non-capped | |
| 45 | 0 | return self.conn.db.collection(self.name, callback); |
| 46 | } | |
| 47 | ||
| 48 | // capped | |
| 49 | 0 | return self.conn.db.collection(self.name, function (err, c) { |
| 50 | 0 | if (err) return callback(err); |
| 51 | ||
| 52 | // discover if this collection exists and if it is capped | |
| 53 | 0 | c.options(function (err, exists) { |
| 54 | 0 | if (err) return callback(err); |
| 55 | ||
| 56 | 0 | if (exists) { |
| 57 | 0 | if (exists.capped) { |
| 58 | 0 | callback(null, c); |
| 59 | } else { | |
| 60 | 0 | var msg = 'A non-capped collection exists with the name: '+ self.name +'\n\n' |
| 61 | + ' To use this collection as a capped collection, please ' | |
| 62 | + 'first convert it.\n' | |
| 63 | + ' http://www.mongodb.org/display/DOCS/Capped+Collections#CappedCollections-Convertingacollectiontocapped' | |
| 64 | 0 | err = new Error(msg); |
| 65 | 0 | callback(err); |
| 66 | } | |
| 67 | } else { | |
| 68 | // create | |
| 69 | 0 | var opts = utils.clone(self.opts.capped); |
| 70 | 0 | opts.capped = true; |
| 71 | 0 | self.conn.db.createCollection(self.name, opts, callback); |
| 72 | } | |
| 73 | }); | |
| 74 | }); | |
| 75 | ||
| 76 | 0 | function callback (err, collection) { |
| 77 | 0 | if (err) { |
| 78 | // likely a strict mode error | |
| 79 | 0 | self.conn.emit('error', err); |
| 80 | } else { | |
| 81 | 0 | self.collection = collection; |
| 82 | 0 | MongooseCollection.prototype.onOpen.call(self); |
| 83 | } | |
| 84 | }; | |
| 85 | }; | |
| 86 | ||
| 87 | /** | |
| 88 | * Called when the connection closes | |
| 89 | * | |
| 90 | * @api private | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | NativeCollection.prototype.onClose = function () { |
| 94 | 0 | MongooseCollection.prototype.onClose.call(this); |
| 95 | }; | |
| 96 | ||
| 97 | /*! | |
| 98 | * Copy the collection methods and make them subject to queues | |
| 99 | */ | |
| 100 | ||
| 101 | 1 | for (var i in Collection.prototype) { |
| 102 | 30 | (function(i){ |
| 103 | 30 | NativeCollection.prototype[i] = function () { |
| 104 | 0 | if (this.buffer) { |
| 105 | 0 | this.addQueue(i, arguments); |
| 106 | 0 | return; |
| 107 | } | |
| 108 | ||
| 109 | 0 | var collection = this.collection |
| 110 | , args = arguments | |
| 111 | , self = this | |
| 112 | , debug = self.conn.base.options.debug; | |
| 113 | ||
| 114 | 0 | if (debug) { |
| 115 | 0 | if ('function' === typeof debug) { |
| 116 | 0 | debug.apply(debug |
| 117 | , [self.name, i].concat(utils.args(args, 0, args.length-1))); | |
| 118 | } else { | |
| 119 | 0 | console.error('\x1B[0;36mMongoose:\x1B[0m %s.%s(%s) %s %s %s' |
| 120 | , self.name | |
| 121 | , i | |
| 122 | , print(args[0]) | |
| 123 | , print(args[1]) | |
| 124 | , print(args[2]) | |
| 125 | , print(args[3])) | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | 0 | return collection[i].apply(collection, args); |
| 130 | }; | |
| 131 | })(i); | |
| 132 | } | |
| 133 | ||
| 134 | /*! | |
| 135 | * Debug print helper | |
| 136 | */ | |
| 137 | ||
| 138 | 1 | function print (arg) { |
| 139 | 0 | var type = typeof arg; |
| 140 | 0 | if ('function' === type || 'undefined' === type) return ''; |
| 141 | 0 | return format(arg); |
| 142 | } | |
| 143 | ||
| 144 | /*! | |
| 145 | * Debug print helper | |
| 146 | */ | |
| 147 | ||
| 148 | 1 | function format (obj, sub) { |
| 149 | 0 | var x = utils.clone(obj); |
| 150 | 0 | if (x) { |
| 151 | 0 | if ('Binary' === x.constructor.name) { |
| 152 | 0 | x = '[object Buffer]'; |
| 153 | 0 | } else if ('ObjectID' === x.constructor.name) { |
| 154 | 0 | var representation = 'ObjectId("' + x.toHexString() + '")'; |
| 155 | 0 | x = { inspect: function() { return representation; } }; |
| 156 | 0 | } else if ('Date' === x.constructor.name) { |
| 157 | 0 | var representation = 'new Date("' + x.toUTCString() + '")'; |
| 158 | 0 | x = { inspect: function() { return representation; } }; |
| 159 | 0 | } else if ('Object' === x.constructor.name) { |
| 160 | 0 | var keys = Object.keys(x) |
| 161 | , i = keys.length | |
| 162 | , key | |
| 163 | 0 | while (i--) { |
| 164 | 0 | key = keys[i]; |
| 165 | 0 | if (x[key]) { |
| 166 | 0 | if ('Binary' === x[key].constructor.name) { |
| 167 | 0 | x[key] = '[object Buffer]'; |
| 168 | 0 | } else if ('Object' === x[key].constructor.name) { |
| 169 | 0 | x[key] = format(x[key], true); |
| 170 | 0 | } else if ('ObjectID' === x[key].constructor.name) { |
| 171 | 0 | ;(function(x){ |
| 172 | 0 | var representation = 'ObjectId("' + x[key].toHexString() + '")'; |
| 173 | 0 | x[key] = { inspect: function() { return representation; } }; |
| 174 | })(x) | |
| 175 | 0 | } else if ('Date' === x[key].constructor.name) { |
| 176 | 0 | ;(function(x){ |
| 177 | 0 | var representation = 'new Date("' + x[key].toUTCString() + '")'; |
| 178 | 0 | x[key] = { inspect: function() { return representation; } }; |
| 179 | })(x) | |
| 180 | 0 | } else if (Array.isArray(x[key])) { |
| 181 | 0 | x[key] = x[key].map(function (o) { |
| 182 | 0 | return format(o, true) |
| 183 | }); | |
| 184 | } | |
| 185 | } | |
| 186 | } | |
| 187 | } | |
| 188 | 0 | if (sub) return x; |
| 189 | } | |
| 190 | ||
| 191 | 0 | return require('util') |
| 192 | .inspect(x, false, 10, true) | |
| 193 | .replace(/\n/g, '') | |
| 194 | .replace(/\s{2,}/g, ' ') | |
| 195 | } | |
| 196 | ||
| 197 | /** | |
| 198 | * Retreives information about this collections indexes. | |
| 199 | * | |
| 200 | * @param {Function} callback | |
| 201 | * @method getIndexes | |
| 202 | * @api public | |
| 203 | */ | |
| 204 | ||
| 205 | 1 | NativeCollection.prototype.getIndexes = NativeCollection.prototype.indexInformation; |
| 206 | ||
| 207 | /*! | |
| 208 | * Module exports. | |
| 209 | */ | |
| 210 | ||
| 211 | 1 | module.exports = NativeCollection; |
| 212 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var MongooseConnection = require('../../connection') |
| 6 | , mongo = require('mongodb') | |
| 7 | , Db = mongo.Db | |
| 8 | , Server = mongo.Server | |
| 9 | , Mongos = mongo.Mongos | |
| 10 | , STATES = require('../../connectionstate') | |
| 11 | , ReplSetServers = mongo.ReplSetServers | |
| 12 | , utils = require('../../utils'); | |
| 13 | ||
| 14 | /** | |
| 15 | * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation. | |
| 16 | * | |
| 17 | * @inherits Connection | |
| 18 | * @api private | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function NativeConnection() { |
| 22 | 1 | MongooseConnection.apply(this, arguments); |
| 23 | 1 | this._listening = false; |
| 24 | }; | |
| 25 | ||
| 26 | /** | |
| 27 | * Expose the possible connection states. | |
| 28 | * @api public | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | NativeConnection.STATES = STATES; |
| 32 | ||
| 33 | /*! | |
| 34 | * Inherits from Connection. | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | NativeConnection.prototype.__proto__ = MongooseConnection.prototype; |
| 38 | ||
| 39 | /** | |
| 40 | * Opens the connection to MongoDB. | |
| 41 | * | |
| 42 | * @param {Function} fn | |
| 43 | * @return {Connection} this | |
| 44 | * @api private | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | NativeConnection.prototype.doOpen = function (fn) { |
| 48 | 1 | if (this.db) { |
| 49 | 0 | mute(this); |
| 50 | } | |
| 51 | ||
| 52 | 1 | var server = new Server(this.host, this.port, this.options.server); |
| 53 | 1 | this.db = new Db(this.name, server, this.options.db); |
| 54 | ||
| 55 | 1 | var self = this; |
| 56 | 1 | this.db.open(function (err) { |
| 57 | 0 | if (err) return fn(err); |
| 58 | 0 | listen(self); |
| 59 | 0 | fn(); |
| 60 | }); | |
| 61 | ||
| 62 | 1 | return this; |
| 63 | }; | |
| 64 | ||
| 65 | /** | |
| 66 | * Switches to a different database using the same connection pool. | |
| 67 | * | |
| 68 | * Returns a new connection object, with the new db. | |
| 69 | * | |
| 70 | * @param {String} name The database name | |
| 71 | * @return {Connection} New Connection Object | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | NativeConnection.prototype.useDb = function (name) { |
| 76 | // we have to manually copy all of the attributes... | |
| 77 | 0 | var newConn = new this.constructor(); |
| 78 | 0 | newConn.name = name; |
| 79 | 0 | newConn.base = this.base; |
| 80 | 0 | newConn.collections = {}; |
| 81 | 0 | newConn.models = {}; |
| 82 | 0 | newConn.replica = this.replica; |
| 83 | 0 | newConn.hosts = this.hosts; |
| 84 | 0 | newConn.host = this.host; |
| 85 | 0 | newConn.port = this.port; |
| 86 | 0 | newConn.user = this.user; |
| 87 | 0 | newConn.pass = this.pass; |
| 88 | 0 | newConn.options = this.options; |
| 89 | 0 | newConn._readyState = this._readyState; |
| 90 | 0 | newConn._closeCalled = this._closeCalled; |
| 91 | 0 | newConn._hasOpened = this._hasOpened; |
| 92 | 0 | newConn._listening = false; |
| 93 | ||
| 94 | // First, when we create another db object, we are not guaranteed to have a | |
| 95 | // db object to work with. So, in the case where we have a db object and it | |
| 96 | // is connected, we can just proceed with setting everything up. However, if | |
| 97 | // we do not have a db or the state is not connected, then we need to wait on | |
| 98 | // the 'open' event of the connection before doing the rest of the setup | |
| 99 | // the 'connected' event is the first time we'll have access to the db object | |
| 100 | ||
| 101 | 0 | var self = this; |
| 102 | ||
| 103 | 0 | if (this.db && this.db._state == 'connected') { |
| 104 | 0 | wireup(); |
| 105 | } else { | |
| 106 | 0 | this.once('connected', wireup); |
| 107 | } | |
| 108 | ||
| 109 | 0 | function wireup () { |
| 110 | 0 | newConn.db = self.db.db(name); |
| 111 | 0 | newConn.onOpen(); |
| 112 | // setup the events appropriately | |
| 113 | 0 | listen(newConn); |
| 114 | } | |
| 115 | ||
| 116 | 0 | newConn.name = name; |
| 117 | ||
| 118 | // push onto the otherDbs stack, this is used when state changes | |
| 119 | 0 | this.otherDbs.push(newConn); |
| 120 | 0 | newConn.otherDbs.push(this); |
| 121 | ||
| 122 | 0 | return newConn; |
| 123 | }; | |
| 124 | ||
| 125 | /*! | |
| 126 | * Register listeners for important events and bubble appropriately. | |
| 127 | */ | |
| 128 | ||
| 129 | 1 | function listen (conn) { |
| 130 | 0 | if (conn._listening) return; |
| 131 | 0 | conn._listening = true; |
| 132 | ||
| 133 | 0 | conn.db.on('close', function(){ |
| 134 | 0 | if (conn._closeCalled) return; |
| 135 | ||
| 136 | // the driver never emits an `open` event. auto_reconnect still | |
| 137 | // emits a `close` event but since we never get another | |
| 138 | // `open` we can't emit close | |
| 139 | 0 | if (conn.db.serverConfig.autoReconnect) { |
| 140 | 0 | conn.readyState = STATES.disconnected; |
| 141 | 0 | conn.emit('close'); |
| 142 | 0 | return; |
| 143 | } | |
| 144 | 0 | conn.onClose(); |
| 145 | }); | |
| 146 | 0 | conn.db.on('error', function(err){ |
| 147 | 0 | conn.emit('error', err); |
| 148 | }); | |
| 149 | 0 | conn.db.on('timeout', function(err){ |
| 150 | 0 | var error = new Error(err && err.err || 'connection timeout'); |
| 151 | 0 | conn.emit('error', error); |
| 152 | }); | |
| 153 | 0 | conn.db.on('open', function (err, db) { |
| 154 | 0 | if (STATES.disconnected === conn.readyState && db && db.databaseName) { |
| 155 | 0 | conn.readyState = STATES.connected; |
| 156 | 0 | conn.emit('reconnected') |
| 157 | } | |
| 158 | }) | |
| 159 | } | |
| 160 | ||
| 161 | /*! | |
| 162 | * Remove listeners registered in `listen` | |
| 163 | */ | |
| 164 | ||
| 165 | 1 | function mute (conn) { |
| 166 | 0 | if (!conn.db) throw new Error('missing db'); |
| 167 | 0 | conn.db.removeAllListeners("close"); |
| 168 | 0 | conn.db.removeAllListeners("error"); |
| 169 | 0 | conn.db.removeAllListeners("timeout"); |
| 170 | 0 | conn.db.removeAllListeners("open"); |
| 171 | 0 | conn.db.removeAllListeners("fullsetup"); |
| 172 | 0 | conn._listening = false; |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * Opens a connection to a MongoDB ReplicaSet. | |
| 177 | * | |
| 178 | * See description of [doOpen](#NativeConnection-doOpen) for server options. In this case `options.replset` is also passed to ReplSetServers. | |
| 179 | * | |
| 180 | * @param {Function} fn | |
| 181 | * @api private | |
| 182 | * @return {Connection} this | |
| 183 | */ | |
| 184 | ||
| 185 | 1 | NativeConnection.prototype.doOpenSet = function (fn) { |
| 186 | 0 | if (this.db) { |
| 187 | 0 | mute(this); |
| 188 | } | |
| 189 | ||
| 190 | 0 | var servers = [] |
| 191 | , self = this; | |
| 192 | ||
| 193 | 0 | this.hosts.forEach(function (server) { |
| 194 | 0 | var host = server.host || server.ipc; |
| 195 | 0 | var port = server.port || 27017; |
| 196 | 0 | servers.push(new Server(host, port, self.options.server)); |
| 197 | }) | |
| 198 | ||
| 199 | 0 | var server = this.options.mongos |
| 200 | ? new Mongos(servers, this.options.mongos) | |
| 201 | : new ReplSetServers(servers, this.options.replset); | |
| 202 | 0 | this.db = new Db(this.name, server, this.options.db); |
| 203 | ||
| 204 | 0 | this.db.on('fullsetup', function () { |
| 205 | 0 | self.emit('fullsetup') |
| 206 | }); | |
| 207 | ||
| 208 | 0 | this.db.open(function (err) { |
| 209 | 0 | if (err) return fn(err); |
| 210 | 0 | fn(); |
| 211 | 0 | listen(self); |
| 212 | }); | |
| 213 | ||
| 214 | 0 | return this; |
| 215 | }; | |
| 216 | ||
| 217 | /** | |
| 218 | * Closes the connection | |
| 219 | * | |
| 220 | * @param {Function} fn | |
| 221 | * @return {Connection} this | |
| 222 | * @api private | |
| 223 | */ | |
| 224 | ||
| 225 | 1 | NativeConnection.prototype.doClose = function (fn) { |
| 226 | 0 | this.db.close(); |
| 227 | 0 | if (fn) fn(); |
| 228 | 0 | return this; |
| 229 | } | |
| 230 | ||
| 231 | /** | |
| 232 | * Prepares default connection options for the node-mongodb-native driver. | |
| 233 | * | |
| 234 | * _NOTE: `passed` options take precedence over connection string options._ | |
| 235 | * | |
| 236 | * @param {Object} passed options that were passed directly during connection | |
| 237 | * @param {Object} [connStrOptions] options that were passed in the connection string | |
| 238 | * @api private | |
| 239 | */ | |
| 240 | ||
| 241 | 1 | NativeConnection.prototype.parseOptions = function (passed, connStrOpts) { |
| 242 | 1 | var o = passed || {}; |
| 243 | 1 | o.db || (o.db = {}); |
| 244 | 1 | o.auth || (o.auth = {}); |
| 245 | 1 | o.server || (o.server = {}); |
| 246 | 1 | o.replset || (o.replset = {}); |
| 247 | 1 | o.server.socketOptions || (o.server.socketOptions = {}); |
| 248 | 1 | o.replset.socketOptions || (o.replset.socketOptions = {}); |
| 249 | ||
| 250 | 1 | var opts = connStrOpts || {}; |
| 251 | 1 | Object.keys(opts).forEach(function (name) { |
| 252 | 0 | switch (name) { |
| 253 | case 'ssl': | |
| 254 | case 'poolSize': | |
| 255 | 0 | if ('undefined' == typeof o.server[name]) { |
| 256 | 0 | o.server[name] = o.replset[name] = opts[name]; |
| 257 | } | |
| 258 | 0 | break; |
| 259 | case 'slaveOk': | |
| 260 | 0 | if ('undefined' == typeof o.server.slave_ok) { |
| 261 | 0 | o.server.slave_ok = opts[name]; |
| 262 | } | |
| 263 | 0 | break; |
| 264 | case 'autoReconnect': | |
| 265 | 0 | if ('undefined' == typeof o.server.auto_reconnect) { |
| 266 | 0 | o.server.auto_reconnect = opts[name]; |
| 267 | } | |
| 268 | 0 | break; |
| 269 | case 'socketTimeoutMS': | |
| 270 | case 'connectTimeoutMS': | |
| 271 | 0 | if ('undefined' == typeof o.server.socketOptions[name]) { |
| 272 | 0 | o.server.socketOptions[name] = o.replset.socketOptions[name] = opts[name]; |
| 273 | } | |
| 274 | 0 | break; |
| 275 | case 'authdb': | |
| 276 | 0 | if ('undefined' == typeof o.auth.authdb) { |
| 277 | 0 | o.auth.authdb = opts[name]; |
| 278 | } | |
| 279 | 0 | break; |
| 280 | case 'authSource': | |
| 281 | 0 | if ('undefined' == typeof o.auth.authSource) { |
| 282 | 0 | o.auth.authSource = opts[name]; |
| 283 | } | |
| 284 | 0 | break; |
| 285 | case 'retries': | |
| 286 | case 'reconnectWait': | |
| 287 | case 'rs_name': | |
| 288 | 0 | if ('undefined' == typeof o.replset[name]) { |
| 289 | 0 | o.replset[name] = opts[name]; |
| 290 | } | |
| 291 | 0 | break; |
| 292 | case 'replicaSet': | |
| 293 | 0 | if ('undefined' == typeof o.replset.rs_name) { |
| 294 | 0 | o.replset.rs_name = opts[name]; |
| 295 | } | |
| 296 | 0 | break; |
| 297 | case 'readSecondary': | |
| 298 | 0 | if ('undefined' == typeof o.replset.read_secondary) { |
| 299 | 0 | o.replset.read_secondary = opts[name]; |
| 300 | } | |
| 301 | 0 | break; |
| 302 | case 'nativeParser': | |
| 303 | 0 | if ('undefined' == typeof o.db.native_parser) { |
| 304 | 0 | o.db.native_parser = opts[name]; |
| 305 | } | |
| 306 | 0 | break; |
| 307 | case 'w': | |
| 308 | case 'safe': | |
| 309 | case 'fsync': | |
| 310 | case 'journal': | |
| 311 | case 'wtimeoutMS': | |
| 312 | 0 | if ('undefined' == typeof o.db[name]) { |
| 313 | 0 | o.db[name] = opts[name]; |
| 314 | } | |
| 315 | 0 | break; |
| 316 | case 'readPreference': | |
| 317 | 0 | if ('undefined' == typeof o.db.read_preference) { |
| 318 | 0 | o.db.read_preference = opts[name]; |
| 319 | } | |
| 320 | 0 | break; |
| 321 | case 'readPreferenceTags': | |
| 322 | 0 | if ('undefined' == typeof o.db.read_preference_tags) { |
| 323 | 0 | o.db.read_preference_tags = opts[name]; |
| 324 | } | |
| 325 | 0 | break; |
| 326 | } | |
| 327 | }) | |
| 328 | ||
| 329 | 1 | if (!('auto_reconnect' in o.server)) { |
| 330 | 1 | o.server.auto_reconnect = true; |
| 331 | } | |
| 332 | ||
| 333 | 1 | if (!o.db.read_preference) { |
| 334 | // read from primaries by default | |
| 335 | 1 | o.db.read_preference = 'primary'; |
| 336 | } | |
| 337 | ||
| 338 | // mongoose creates its own ObjectIds | |
| 339 | 1 | o.db.forceServerObjectId = false; |
| 340 | ||
| 341 | // default safe using new nomenclature | |
| 342 | 1 | if (!('journal' in o.db || 'j' in o.db || |
| 343 | 'fsync' in o.db || 'safe' in o.db || 'w' in o.db)) { | |
| 344 | 1 | o.db.w = 1; |
| 345 | } | |
| 346 | ||
| 347 | 1 | validate(o); |
| 348 | 1 | return o; |
| 349 | } | |
| 350 | ||
| 351 | /*! | |
| 352 | * Validates the driver db options. | |
| 353 | * | |
| 354 | * @param {Object} o | |
| 355 | */ | |
| 356 | ||
| 357 | 1 | function validate (o) { |
| 358 | 1 | if (-1 === o.db.w || 0 === o.db.w) { |
| 359 | 0 | if (o.db.journal || o.db.fsync || o.db.safe) { |
| 360 | 0 | throw new Error( |
| 361 | 'Invalid writeConcern: ' | |
| 362 | + 'w set to -1 or 0 cannot be combined with safe|fsync|journal'); | |
| 363 | } | |
| 364 | } | |
| 365 | } | |
| 366 | ||
| 367 | /*! | |
| 368 | * Module exports. | |
| 369 | */ | |
| 370 | ||
| 371 | 1 | module.exports = NativeConnection; |
| 372 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) ObjectId | |
| 4 | * @constructor NodeMongoDbObjectId | |
| 5 | * @see ObjectId | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var ObjectId = require('mongodb').BSONPure.ObjectID; |
| 9 | ||
| 10 | /*! | |
| 11 | * ignore | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | module.exports = exports = ObjectId; |
| 15 | ||
| 16 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * MongooseError constructor | |
| 4 | * | |
| 5 | * @param {String} msg Error message | |
| 6 | * @inherits Error https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error | |
| 7 | */ | |
| 8 | ||
| 9 | 1 | function MongooseError (msg) { |
| 10 | 0 | Error.call(this); |
| 11 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 12 | 0 | this.message = msg; |
| 13 | 0 | this.name = 'MongooseError'; |
| 14 | }; | |
| 15 | ||
| 16 | /*! | |
| 17 | * Formats error messages | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | MongooseError.prototype.formatMessage = function (msg, path, type, val) { |
| 21 | 0 | if (!msg) throw new TypeError('message is required'); |
| 22 | ||
| 23 | 0 | return msg.replace(/{PATH}/, path) |
| 24 | .replace(/{VALUE}/, String(val||'')) | |
| 25 | .replace(/{TYPE}/, type || 'declared type'); | |
| 26 | } | |
| 27 | ||
| 28 | /*! | |
| 29 | * Inherits from Error. | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | MongooseError.prototype.__proto__ = Error.prototype; |
| 33 | ||
| 34 | /*! | |
| 35 | * Module exports. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | module.exports = exports = MongooseError; |
| 39 | ||
| 40 | /** | |
| 41 | * The default built-in validator error messages. | |
| 42 | * | |
| 43 | * @see Error.messages #error_messages_MongooseError-messages | |
| 44 | * @api public | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | MongooseError.messages = require('./error/messages'); |
| 48 | ||
| 49 | // backward compat | |
| 50 | 1 | MongooseError.Messages = MongooseError.messages; |
| 51 | ||
| 52 | /*! | |
| 53 | * Expose subclasses | |
| 54 | */ | |
| 55 | ||
| 56 | 1 | MongooseError.CastError = require('./error/cast'); |
| 57 | 1 | MongooseError.ValidationError = require('./error/validation') |
| 58 | 1 | MongooseError.ValidatorError = require('./error/validator') |
| 59 | 1 | MongooseError.VersionError =require('./error/version') |
| 60 | 1 | MongooseError.OverwriteModelError = require('./error/overwriteModel') |
| 61 | 1 | MongooseError.MissingSchemaError = require('./error/missingSchema') |
| 62 | 1 | MongooseError.DivergentArrayError = require('./error/divergentArray') |
| 63 | ||
| 64 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var MongooseError = require('../error.js'); |
| 6 | ||
| 7 | /** | |
| 8 | * Casting Error constructor. | |
| 9 | * | |
| 10 | * @param {String} type | |
| 11 | * @param {String} value | |
| 12 | * @inherits MongooseError | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | function CastError (type, value, path) { |
| 17 | 0 | MongooseError.call(this, 'Cast to ' + type + ' failed for value "' + value + '" at path "' + path + '"'); |
| 18 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 19 | 0 | this.name = 'CastError'; |
| 20 | 0 | this.type = type; |
| 21 | 0 | this.value = value; |
| 22 | 0 | this.path = path; |
| 23 | }; | |
| 24 | ||
| 25 | /*! | |
| 26 | * Inherits from MongooseError. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | CastError.prototype.__proto__ = MongooseError.prototype; |
| 30 | ||
| 31 | /*! | |
| 32 | * exports | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | module.exports = CastError; |
| 36 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseError = require('../error.js'); |
| 7 | ||
| 8 | /*! | |
| 9 | * DivergentArrayError constructor. | |
| 10 | * | |
| 11 | * @inherits MongooseError | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | function DivergentArrayError (paths) { |
| 15 | 0 | var msg = 'For your own good, using `document.save()` to update an array ' |
| 16 | + 'which was selected using an $elemMatch projection OR ' | |
| 17 | + 'populated using skip, limit, query conditions, or exclusion of ' | |
| 18 | + 'the _id field when the operation results in a $pop or $set of ' | |
| 19 | + 'the entire array is not supported. The following ' | |
| 20 | + 'path(s) would have been modified unsafely:\n' | |
| 21 | + ' ' + paths.join('\n ') + '\n' | |
| 22 | + 'Use Model.update() to update these arrays instead.' | |
| 23 | // TODO write up a docs page (FAQ) and link to it | |
| 24 | ||
| 25 | 0 | MongooseError.call(this, msg); |
| 26 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 27 | 0 | this.name = 'DivergentArrayError'; |
| 28 | }; | |
| 29 | ||
| 30 | /*! | |
| 31 | * Inherits from MongooseError. | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | DivergentArrayError.prototype.__proto__ = MongooseError.prototype; |
| 35 | ||
| 36 | /*! | |
| 37 | * exports | |
| 38 | */ | |
| 39 | ||
| 40 | 1 | module.exports = DivergentArrayError; |
| 41 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * The default built-in validator error messages. These may be customized. | |
| 4 | * | |
| 5 | * // customize within each schema or globally like so | |
| 6 | * var mongoose = require('mongoose'); | |
| 7 | * mongoose.Error.messages.String.enum = "Your custom message for {PATH}."; | |
| 8 | * | |
| 9 | * As you might have noticed, error messages support basic templating | |
| 10 | * | |
| 11 | * - `{PATH}` is replaced with the invalid document path | |
| 12 | * - `{VALUE}` is replaced with the invalid value | |
| 13 | * - `{TYPE}` is replaced with the validator type such as "regexp", "min", or "user defined" | |
| 14 | * - `{MIN}` is replaced with the declared min value for the Number.min validator | |
| 15 | * - `{MAX}` is replaced with the declared max value for the Number.max validator | |
| 16 | * | |
| 17 | * Click the "show code" link below to see all defaults. | |
| 18 | * | |
| 19 | * @property messages | |
| 20 | * @receiver MongooseError | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | var msg = module.exports = exports = {}; |
| 25 | ||
| 26 | 1 | msg.general = {}; |
| 27 | 1 | msg.general.default = "Validator failed for path `{PATH}` with value `{VALUE}`"; |
| 28 | 1 | msg.general.required = "Path `{PATH}` is required."; |
| 29 | ||
| 30 | 1 | msg.Number = {}; |
| 31 | 1 | msg.Number.min = "Path `{PATH}` ({VALUE}) is less than minimum allowed value ({MIN})."; |
| 32 | 1 | msg.Number.max = "Path `{PATH}` ({VALUE}) is more than maximum allowed value ({MAX})."; |
| 33 | ||
| 34 | 1 | msg.String = {}; |
| 35 | 1 | msg.String.enum = "`{VALUE}` is not a valid enum value for path `{PATH}`."; |
| 36 | 1 | msg.String.match = "Path `{PATH}` is invalid ({VALUE})."; |
| 37 | ||
| 38 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseError = require('../error.js'); |
| 7 | ||
| 8 | /*! | |
| 9 | * MissingSchema Error constructor. | |
| 10 | * | |
| 11 | * @inherits MongooseError | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | function MissingSchemaError (name) { |
| 15 | 0 | var msg = 'Schema hasn\'t been registered for model "' + name + '".\n' |
| 16 | + 'Use mongoose.model(name, schema)'; | |
| 17 | 0 | MongooseError.call(this, msg); |
| 18 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 19 | 0 | this.name = 'MissingSchemaError'; |
| 20 | }; | |
| 21 | ||
| 22 | /*! | |
| 23 | * Inherits from MongooseError. | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | MissingSchemaError.prototype.__proto__ = MongooseError.prototype; |
| 27 | ||
| 28 | /*! | |
| 29 | * exports | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | module.exports = MissingSchemaError; |
| 33 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseError = require('../error.js'); |
| 7 | ||
| 8 | /*! | |
| 9 | * OverwriteModel Error constructor. | |
| 10 | * | |
| 11 | * @inherits MongooseError | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | function OverwriteModelError (name) { |
| 15 | 0 | MongooseError.call(this, 'Cannot overwrite `' + name + '` model once compiled.'); |
| 16 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 17 | 0 | this.name = 'OverwriteModelError'; |
| 18 | }; | |
| 19 | ||
| 20 | /*! | |
| 21 | * Inherits from MongooseError. | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | OverwriteModelError.prototype.__proto__ = MongooseError.prototype; |
| 25 | ||
| 26 | /*! | |
| 27 | * exports | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | module.exports = OverwriteModelError; |
| 31 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module requirements | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseError = require('../error.js') |
| 7 | ||
| 8 | /** | |
| 9 | * Document Validation Error | |
| 10 | * | |
| 11 | * @api private | |
| 12 | * @param {Document} instance | |
| 13 | * @inherits MongooseError | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | function ValidationError (instance) { |
| 17 | 0 | MongooseError.call(this, "Validation failed"); |
| 18 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 19 | 0 | this.name = 'ValidationError'; |
| 20 | 0 | this.errors = instance.errors = {}; |
| 21 | }; | |
| 22 | ||
| 23 | /** | |
| 24 | * Console.log helper | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | ValidationError.prototype.toString = function () { |
| 28 | 0 | var ret = this.name + ': '; |
| 29 | 0 | var msgs = []; |
| 30 | ||
| 31 | 0 | Object.keys(this.errors).forEach(function (key) { |
| 32 | 0 | if (this == this.errors[key]) return; |
| 33 | 0 | msgs.push(String(this.errors[key])); |
| 34 | }, this) | |
| 35 | ||
| 36 | 0 | return ret + msgs.join(', '); |
| 37 | }; | |
| 38 | ||
| 39 | /*! | |
| 40 | * Inherits from MongooseError. | |
| 41 | */ | |
| 42 | ||
| 43 | 1 | ValidationError.prototype.__proto__ = MongooseError.prototype; |
| 44 | ||
| 45 | /*! | |
| 46 | * Module exports | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | module.exports = exports = ValidationError; |
| 50 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var MongooseError = require('../error.js'); |
| 6 | 1 | var errorMessages = MongooseError.messages; |
| 7 | ||
| 8 | /** | |
| 9 | * Schema validator error | |
| 10 | * | |
| 11 | * @param {String} path | |
| 12 | * @param {String} msg | |
| 13 | * @param {String|Number|any} val | |
| 14 | * @inherits MongooseError | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | function ValidatorError (path, msg, type, val) { |
| 19 | 0 | if (!msg) msg = errorMessages.general.default; |
| 20 | 0 | var message = this.formatMessage(msg, path, type, val); |
| 21 | 0 | MongooseError.call(this, message); |
| 22 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 23 | 0 | this.name = 'ValidatorError'; |
| 24 | 0 | this.path = path; |
| 25 | 0 | this.type = type; |
| 26 | 0 | this.value = val; |
| 27 | }; | |
| 28 | ||
| 29 | /*! | |
| 30 | * toString helper | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | ValidatorError.prototype.toString = function () { |
| 34 | 0 | return this.message; |
| 35 | } | |
| 36 | ||
| 37 | /*! | |
| 38 | * Inherits from MongooseError | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | ValidatorError.prototype.__proto__ = MongooseError.prototype; |
| 42 | ||
| 43 | /*! | |
| 44 | * exports | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | module.exports = ValidatorError; |
| 48 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseError = require('../error.js'); |
| 7 | ||
| 8 | /** | |
| 9 | * Version Error constructor. | |
| 10 | * | |
| 11 | * @inherits MongooseError | |
| 12 | * @api private | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | function VersionError () { |
| 16 | 0 | MongooseError.call(this, 'No matching document found.'); |
| 17 | 0 | Error.captureStackTrace(this, arguments.callee); |
| 18 | 0 | this.name = 'VersionError'; |
| 19 | }; | |
| 20 | ||
| 21 | /*! | |
| 22 | * Inherits from MongooseError. | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | VersionError.prototype.__proto__ = MongooseError.prototype; |
| 26 | ||
| 27 | /*! | |
| 28 | * exports | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | module.exports = VersionError; |
| 32 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /*! | |
| 4 | * Module dependencies. | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var Schema = require('./schema') |
| 8 | , SchemaType = require('./schematype') | |
| 9 | , VirtualType = require('./virtualtype') | |
| 10 | , SchemaDefaults = require('./schemadefault') | |
| 11 | , STATES = require('./connectionstate') | |
| 12 | , Types = require('./types') | |
| 13 | , Query = require('./query') | |
| 14 | , Promise = require('./promise') | |
| 15 | , Model = require('./model') | |
| 16 | , Document = require('./document') | |
| 17 | , utils = require('./utils') | |
| 18 | , format = utils.toCollectionName | |
| 19 | , mongodb = require('mongodb') | |
| 20 | , pkg = require('../package.json') | |
| 21 | ||
| 22 | /*! | |
| 23 | * Warn users if they are running an unstable release. | |
| 24 | * | |
| 25 | * Disable the warning by setting the MONGOOSE_DISABLE_STABILITY_WARNING | |
| 26 | * environment variable. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | if (pkg.publishConfig && 'unstable' == pkg.publishConfig.tag) { |
| 30 | 0 | if (!process.env.MONGOOSE_DISABLE_STABILITY_WARNING) { |
| 31 | 0 | console.log('\u001b[33m'); |
| 32 | 0 | console.log('##############################################################'); |
| 33 | 0 | console.log('#'); |
| 34 | 0 | console.log('# !!! MONGOOSE WARNING !!!'); |
| 35 | 0 | console.log('#'); |
| 36 | 0 | console.log('# This is an UNSTABLE release of Mongoose.'); |
| 37 | 0 | console.log('# Unstable releases are available for preview/testing only.'); |
| 38 | 0 | console.log('# DO NOT run this in production.'); |
| 39 | 0 | console.log('#'); |
| 40 | 0 | console.log('##############################################################'); |
| 41 | 0 | console.log('\u001b[0m'); |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | /** | |
| 46 | * Mongoose constructor. | |
| 47 | * | |
| 48 | * The exports object of the `mongoose` module is an instance of this class. | |
| 49 | * Most apps will only use this one instance. | |
| 50 | * | |
| 51 | * @api public | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | function Mongoose () { |
| 55 | 1 | this.connections = []; |
| 56 | 1 | this.plugins = []; |
| 57 | 1 | this.models = {}; |
| 58 | 1 | this.modelSchemas = {}; |
| 59 | // default global options | |
| 60 | 1 | this.options = { |
| 61 | pluralization: true | |
| 62 | }; | |
| 63 | 1 | this.createConnection(); // default connection |
| 64 | }; | |
| 65 | ||
| 66 | /** | |
| 67 | * Expose connection states for user-land | |
| 68 | * | |
| 69 | */ | |
| 70 | 1 | Mongoose.prototype.STATES = STATES; |
| 71 | ||
| 72 | /** | |
| 73 | * Sets mongoose options | |
| 74 | * | |
| 75 | * ####Example: | |
| 76 | * | |
| 77 | * mongoose.set('test', value) // sets the 'test' option to `value` | |
| 78 | * | |
| 79 | * mongoose.set('debug', true) // enable logging collection methods + arguments to the console | |
| 80 | * | |
| 81 | * @param {String} key | |
| 82 | * @param {String} value | |
| 83 | * @api public | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | Mongoose.prototype.set = function (key, value) { |
| 87 | 0 | if (arguments.length == 1) { |
| 88 | 0 | return this.options[key]; |
| 89 | } | |
| 90 | ||
| 91 | 0 | this.options[key] = value; |
| 92 | 0 | return this; |
| 93 | }; | |
| 94 | ||
| 95 | /** | |
| 96 | * Gets mongoose options | |
| 97 | * | |
| 98 | * ####Example: | |
| 99 | * | |
| 100 | * mongoose.get('test') // returns the 'test' value | |
| 101 | * | |
| 102 | * @param {String} key | |
| 103 | * @method get | |
| 104 | * @api public | |
| 105 | */ | |
| 106 | ||
| 107 | 1 | Mongoose.prototype.get = Mongoose.prototype.set; |
| 108 | ||
| 109 | /*! | |
| 110 | * ReplSet connection string check. | |
| 111 | */ | |
| 112 | ||
| 113 | 1 | var rgxReplSet = /^.+,.+$/; |
| 114 | ||
| 115 | /** | |
| 116 | * Creates a Connection instance. | |
| 117 | * | |
| 118 | * Each `connection` instance maps to a single database. This method is helpful when mangaging multiple db connections. | |
| 119 | * | |
| 120 | * If arguments are passed, they are proxied to either [Connection#open](#connection_Connection-open) or [Connection#openSet](#connection_Connection-openSet) appropriately. This means we can pass `db`, `server`, and `replset` options to the driver. _Note that the `safe` option specified in your schema will overwrite the `safe` db option specified here unless you set your schemas `safe` option to `undefined`. See [this](/docs/guide.html#safe) for more information._ | |
| 121 | * | |
| 122 | * _Options passed take precedence over options included in connection strings._ | |
| 123 | * | |
| 124 | * ####Example: | |
| 125 | * | |
| 126 | * // with mongodb:// URI | |
| 127 | * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database'); | |
| 128 | * | |
| 129 | * // and options | |
| 130 | * var opts = { db: { native_parser: true }} | |
| 131 | * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database', opts); | |
| 132 | * | |
| 133 | * // replica sets | |
| 134 | * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database,mongodb://anotherhost:port,mongodb://yetanother:port'); | |
| 135 | * | |
| 136 | * // and options | |
| 137 | * var opts = { replset: { strategy: 'ping', rs_name: 'testSet' }} | |
| 138 | * db = mongoose.createConnection('mongodb://user:pass@localhost:port/database,mongodb://anotherhost:port,mongodb://yetanother:port', opts); | |
| 139 | * | |
| 140 | * // with [host, database_name[, port] signature | |
| 141 | * db = mongoose.createConnection('localhost', 'database', port) | |
| 142 | * | |
| 143 | * // and options | |
| 144 | * var opts = { server: { auto_reconnect: false }, user: 'username', pass: 'mypassword' } | |
| 145 | * db = mongoose.createConnection('localhost', 'database', port, opts) | |
| 146 | * | |
| 147 | * // initialize now, connect later | |
| 148 | * db = mongoose.createConnection(); | |
| 149 | * db.open('localhost', 'database', port, [opts]); | |
| 150 | * | |
| 151 | * @param {String} [uri] a mongodb:// URI | |
| 152 | * @param {Object} [options] options to pass to the driver | |
| 153 | * @see Connection#open #connection_Connection-open | |
| 154 | * @see Connection#openSet #connection_Connection-openSet | |
| 155 | * @return {Connection} the created Connection object | |
| 156 | * @api public | |
| 157 | */ | |
| 158 | ||
| 159 | 1 | Mongoose.prototype.createConnection = function () { |
| 160 | 1 | var conn = new Connection(this); |
| 161 | 1 | this.connections.push(conn); |
| 162 | ||
| 163 | 1 | if (arguments.length) { |
| 164 | 0 | if (rgxReplSet.test(arguments[0])) { |
| 165 | 0 | conn.openSet.apply(conn, arguments); |
| 166 | } else { | |
| 167 | 0 | conn.open.apply(conn, arguments); |
| 168 | } | |
| 169 | } | |
| 170 | ||
| 171 | 1 | return conn; |
| 172 | }; | |
| 173 | ||
| 174 | /** | |
| 175 | * Opens the default mongoose connection. | |
| 176 | * | |
| 177 | * If arguments are passed, they are proxied to either [Connection#open](#connection_Connection-open) or [Connection#openSet](#connection_Connection-openSet) appropriately. | |
| 178 | * | |
| 179 | * _Options passed take precedence over options included in connection strings._ | |
| 180 | * | |
| 181 | * ####Example: | |
| 182 | * | |
| 183 | * mongoose.connect('mongodb://user:pass@localhost:port/database'); | |
| 184 | * | |
| 185 | * // replica sets | |
| 186 | * var uri = 'mongodb://user:pass@localhost:port/database,mongodb://anotherhost:port,mongodb://yetanother:port'; | |
| 187 | * mongoose.connect(uri); | |
| 188 | * | |
| 189 | * // with options | |
| 190 | * mongoose.connect(uri, options); | |
| 191 | * | |
| 192 | * // connecting to multiple mongos | |
| 193 | * var uri = 'mongodb://hostA:27501,hostB:27501'; | |
| 194 | * var opts = { mongos: true }; | |
| 195 | * mongoose.connect(uri, opts); | |
| 196 | * | |
| 197 | * @param {String} uri(s) | |
| 198 | * @param {Object} [options] | |
| 199 | * @param {Function} [callback] | |
| 200 | * @see Mongoose#createConnection #index_Mongoose-createConnection | |
| 201 | * @api public | |
| 202 | * @return {Mongoose} this | |
| 203 | */ | |
| 204 | ||
| 205 | 1 | Mongoose.prototype.connect = function () { |
| 206 | 1 | var conn = this.connection; |
| 207 | ||
| 208 | 1 | if (rgxReplSet.test(arguments[0])) { |
| 209 | 0 | conn.openSet.apply(conn, arguments); |
| 210 | } else { | |
| 211 | 1 | conn.open.apply(conn, arguments); |
| 212 | } | |
| 213 | ||
| 214 | 1 | return this; |
| 215 | }; | |
| 216 | ||
| 217 | /** | |
| 218 | * Disconnects all connections. | |
| 219 | * | |
| 220 | * @param {Function} [fn] called after all connection close. | |
| 221 | * @return {Mongoose} this | |
| 222 | * @api public | |
| 223 | */ | |
| 224 | ||
| 225 | 1 | Mongoose.prototype.disconnect = function (fn) { |
| 226 | 0 | var count = this.connections.length |
| 227 | , error | |
| 228 | ||
| 229 | 0 | this.connections.forEach(function(conn){ |
| 230 | 0 | conn.close(function(err){ |
| 231 | 0 | if (error) return; |
| 232 | ||
| 233 | 0 | if (err) { |
| 234 | 0 | error = err; |
| 235 | 0 | if (fn) return fn(err); |
| 236 | 0 | throw err; |
| 237 | } | |
| 238 | ||
| 239 | 0 | if (fn) |
| 240 | 0 | --count || fn(); |
| 241 | }); | |
| 242 | }); | |
| 243 | 0 | return this; |
| 244 | }; | |
| 245 | ||
| 246 | /** | |
| 247 | * Defines a model or retrieves it. | |
| 248 | * | |
| 249 | * Models defined on the `mongoose` instance are available to all connection created by the same `mongoose` instance. | |
| 250 | * | |
| 251 | * ####Example: | |
| 252 | * | |
| 253 | * var mongoose = require('mongoose'); | |
| 254 | * | |
| 255 | * // define an Actor model with this mongoose instance | |
| 256 | * mongoose.model('Actor', new Schema({ name: String })); | |
| 257 | * | |
| 258 | * // create a new connection | |
| 259 | * var conn = mongoose.createConnection(..); | |
| 260 | * | |
| 261 | * // retrieve the Actor model | |
| 262 | * var Actor = conn.model('Actor'); | |
| 263 | * | |
| 264 | * _When no `collection` argument is passed, Mongoose produces a collection name by passing the model `name` to the [utils.toCollectionName](#utils_exports.toCollectionName) method. This method pluralizes the name. If you don't like this behavior, either pass a collection name or set your schemas collection name option._ | |
| 265 | * | |
| 266 | * ####Example: | |
| 267 | * | |
| 268 | * var schema = new Schema({ name: String }, { collection: 'actor' }); | |
| 269 | * | |
| 270 | * // or | |
| 271 | * | |
| 272 | * schema.set('collection', 'actor'); | |
| 273 | * | |
| 274 | * // or | |
| 275 | * | |
| 276 | * var collectionName = 'actor' | |
| 277 | * var M = mongoose.model('Actor', schema, collectionName) | |
| 278 | * | |
| 279 | * @param {String} name model name | |
| 280 | * @param {Schema} [schema] | |
| 281 | * @param {String} [collection] name (optional, induced from model name) | |
| 282 | * @param {Boolean} [skipInit] whether to skip initialization (defaults to false) | |
| 283 | * @api public | |
| 284 | */ | |
| 285 | ||
| 286 | 1 | Mongoose.prototype.model = function (name, schema, collection, skipInit) { |
| 287 | 0 | if ('string' == typeof schema) { |
| 288 | 0 | collection = schema; |
| 289 | 0 | schema = false; |
| 290 | } | |
| 291 | ||
| 292 | 0 | if (utils.isObject(schema) && !(schema instanceof Schema)) { |
| 293 | 0 | schema = new Schema(schema); |
| 294 | } | |
| 295 | ||
| 296 | 0 | if ('boolean' === typeof collection) { |
| 297 | 0 | skipInit = collection; |
| 298 | 0 | collection = null; |
| 299 | } | |
| 300 | ||
| 301 | // handle internal options from connection.model() | |
| 302 | 0 | var options; |
| 303 | 0 | if (skipInit && utils.isObject(skipInit)) { |
| 304 | 0 | options = skipInit; |
| 305 | 0 | skipInit = true; |
| 306 | } else { | |
| 307 | 0 | options = {}; |
| 308 | } | |
| 309 | ||
| 310 | // look up schema for the collection. this might be a | |
| 311 | // default schema like system.indexes stored in SchemaDefaults. | |
| 312 | 0 | if (!this.modelSchemas[name]) { |
| 313 | 0 | if (!schema && name in SchemaDefaults) { |
| 314 | 0 | schema = SchemaDefaults[name]; |
| 315 | } | |
| 316 | ||
| 317 | 0 | if (schema) { |
| 318 | // cache it so we only apply plugins once | |
| 319 | 0 | this.modelSchemas[name] = schema; |
| 320 | 0 | this._applyPlugins(schema); |
| 321 | } else { | |
| 322 | 0 | throw new mongoose.Error.MissingSchemaError(name); |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | 0 | var model; |
| 327 | 0 | var sub; |
| 328 | ||
| 329 | // connection.model() may be passing a different schema for | |
| 330 | // an existing model name. in this case don't read from cache. | |
| 331 | 0 | if (this.models[name] && false !== options.cache) { |
| 332 | 0 | if (schema instanceof Schema && schema != this.models[name].schema) { |
| 333 | 0 | throw new mongoose.Error.OverwriteModelError(name); |
| 334 | } | |
| 335 | ||
| 336 | 0 | if (collection) { |
| 337 | // subclass current model with alternate collection | |
| 338 | 0 | model = this.models[name]; |
| 339 | 0 | schema = model.prototype.schema; |
| 340 | 0 | sub = model.__subclass(this.connection, schema, collection); |
| 341 | // do not cache the sub model | |
| 342 | 0 | return sub; |
| 343 | } | |
| 344 | ||
| 345 | 0 | return this.models[name]; |
| 346 | } | |
| 347 | ||
| 348 | // ensure a schema exists | |
| 349 | 0 | if (!schema) { |
| 350 | 0 | schema = this.modelSchemas[name]; |
| 351 | 0 | if (!schema) { |
| 352 | 0 | throw new mongoose.Error.MissingSchemaError(name); |
| 353 | } | |
| 354 | } | |
| 355 | ||
| 356 | // Apply relevant "global" options to the schema | |
| 357 | 0 | if (!('pluralization' in schema.options)) schema.options.pluralization = this.options.pluralization; |
| 358 | ||
| 359 | ||
| 360 | 0 | if (!collection) { |
| 361 | 0 | collection = schema.get('collection') || format(name, schema.options); |
| 362 | } | |
| 363 | ||
| 364 | 0 | var connection = options.connection || this.connection; |
| 365 | 0 | model = Model.compile(name, schema, collection, connection, this); |
| 366 | ||
| 367 | 0 | if (!skipInit) { |
| 368 | 0 | model.init(); |
| 369 | } | |
| 370 | ||
| 371 | 0 | if (false === options.cache) { |
| 372 | 0 | return model; |
| 373 | } | |
| 374 | ||
| 375 | 0 | return this.models[name] = model; |
| 376 | } | |
| 377 | ||
| 378 | /** | |
| 379 | * Returns an array of model names created on this instance of Mongoose. | |
| 380 | * | |
| 381 | * ####Note: | |
| 382 | * | |
| 383 | * _Does not include names of models created using `connection.model()`._ | |
| 384 | * | |
| 385 | * @api public | |
| 386 | * @return {Array} | |
| 387 | */ | |
| 388 | ||
| 389 | 1 | Mongoose.prototype.modelNames = function () { |
| 390 | 0 | var names = Object.keys(this.models); |
| 391 | 0 | return names; |
| 392 | } | |
| 393 | ||
| 394 | /** | |
| 395 | * Applies global plugins to `schema`. | |
| 396 | * | |
| 397 | * @param {Schema} schema | |
| 398 | * @api private | |
| 399 | */ | |
| 400 | ||
| 401 | 1 | Mongoose.prototype._applyPlugins = function (schema) { |
| 402 | 0 | for (var i = 0, l = this.plugins.length; i < l; i++) { |
| 403 | 0 | schema.plugin(this.plugins[i][0], this.plugins[i][1]); |
| 404 | } | |
| 405 | } | |
| 406 | ||
| 407 | /** | |
| 408 | * Declares a global plugin executed on all Schemas. | |
| 409 | * | |
| 410 | * Equivalent to calling `.plugin(fn)` on each Schema you create. | |
| 411 | * | |
| 412 | * @param {Function} fn plugin callback | |
| 413 | * @param {Object} [opts] optional options | |
| 414 | * @return {Mongoose} this | |
| 415 | * @see plugins ./plugins.html | |
| 416 | * @api public | |
| 417 | */ | |
| 418 | ||
| 419 | 1 | Mongoose.prototype.plugin = function (fn, opts) { |
| 420 | 0 | this.plugins.push([fn, opts]); |
| 421 | 0 | return this; |
| 422 | }; | |
| 423 | ||
| 424 | /** | |
| 425 | * The default connection of the mongoose module. | |
| 426 | * | |
| 427 | * ####Example: | |
| 428 | * | |
| 429 | * var mongoose = require('mongoose'); | |
| 430 | * mongoose.connect(...); | |
| 431 | * mongoose.connection.on('error', cb); | |
| 432 | * | |
| 433 | * This is the connection used by default for every model created using [mongoose.model](#index_Mongoose-model). | |
| 434 | * | |
| 435 | * @property connection | |
| 436 | * @return {Connection} | |
| 437 | * @api public | |
| 438 | */ | |
| 439 | ||
| 440 | 1 | Mongoose.prototype.__defineGetter__('connection', function(){ |
| 441 | 3 | return this.connections[0]; |
| 442 | }); | |
| 443 | ||
| 444 | /*! | |
| 445 | * Driver depentend APIs | |
| 446 | */ | |
| 447 | ||
| 448 | 1 | var driver = global.MONGOOSE_DRIVER_PATH || './drivers/node-mongodb-native'; |
| 449 | ||
| 450 | /*! | |
| 451 | * Connection | |
| 452 | */ | |
| 453 | ||
| 454 | 1 | var Connection = require(driver + '/connection'); |
| 455 | ||
| 456 | /*! | |
| 457 | * Collection | |
| 458 | */ | |
| 459 | ||
| 460 | 1 | var Collection = require(driver + '/collection'); |
| 461 | ||
| 462 | /** | |
| 463 | * The Mongoose Collection constructor | |
| 464 | * | |
| 465 | * @method Collection | |
| 466 | * @api public | |
| 467 | */ | |
| 468 | ||
| 469 | 1 | Mongoose.prototype.Collection = Collection; |
| 470 | ||
| 471 | /** | |
| 472 | * The Mongoose [Connection](#connection_Connection) constructor | |
| 473 | * | |
| 474 | * @method Connection | |
| 475 | * @api public | |
| 476 | */ | |
| 477 | ||
| 478 | 1 | Mongoose.prototype.Connection = Connection; |
| 479 | ||
| 480 | /** | |
| 481 | * The Mongoose version | |
| 482 | * | |
| 483 | * @property version | |
| 484 | * @api public | |
| 485 | */ | |
| 486 | ||
| 487 | 1 | Mongoose.prototype.version = pkg.version; |
| 488 | ||
| 489 | /** | |
| 490 | * The Mongoose constructor | |
| 491 | * | |
| 492 | * The exports of the mongoose module is an instance of this class. | |
| 493 | * | |
| 494 | * ####Example: | |
| 495 | * | |
| 496 | * var mongoose = require('mongoose'); | |
| 497 | * var mongoose2 = new mongoose.Mongoose(); | |
| 498 | * | |
| 499 | * @method Mongoose | |
| 500 | * @api public | |
| 501 | */ | |
| 502 | ||
| 503 | 1 | Mongoose.prototype.Mongoose = Mongoose; |
| 504 | ||
| 505 | /** | |
| 506 | * The Mongoose [Schema](#schema_Schema) constructor | |
| 507 | * | |
| 508 | * ####Example: | |
| 509 | * | |
| 510 | * var mongoose = require('mongoose'); | |
| 511 | * var Schema = mongoose.Schema; | |
| 512 | * var CatSchema = new Schema(..); | |
| 513 | * | |
| 514 | * @method Schema | |
| 515 | * @api public | |
| 516 | */ | |
| 517 | ||
| 518 | 1 | Mongoose.prototype.Schema = Schema; |
| 519 | ||
| 520 | /** | |
| 521 | * The Mongoose [SchemaType](#schematype_SchemaType) constructor | |
| 522 | * | |
| 523 | * @method SchemaType | |
| 524 | * @api public | |
| 525 | */ | |
| 526 | ||
| 527 | 1 | Mongoose.prototype.SchemaType = SchemaType; |
| 528 | ||
| 529 | /** | |
| 530 | * The various Mongoose SchemaTypes. | |
| 531 | * | |
| 532 | * ####Note: | |
| 533 | * | |
| 534 | * _Alias of mongoose.Schema.Types for backwards compatibility._ | |
| 535 | * | |
| 536 | * @property SchemaTypes | |
| 537 | * @see Schema.SchemaTypes #schema_Schema.Types | |
| 538 | * @api public | |
| 539 | */ | |
| 540 | ||
| 541 | 1 | Mongoose.prototype.SchemaTypes = Schema.Types; |
| 542 | ||
| 543 | /** | |
| 544 | * The Mongoose [VirtualType](#virtualtype_VirtualType) constructor | |
| 545 | * | |
| 546 | * @method VirtualType | |
| 547 | * @api public | |
| 548 | */ | |
| 549 | ||
| 550 | 1 | Mongoose.prototype.VirtualType = VirtualType; |
| 551 | ||
| 552 | /** | |
| 553 | * The various Mongoose Types. | |
| 554 | * | |
| 555 | * ####Example: | |
| 556 | * | |
| 557 | * var mongoose = require('mongoose'); | |
| 558 | * var array = mongoose.Types.Array; | |
| 559 | * | |
| 560 | * ####Types: | |
| 561 | * | |
| 562 | * - [ObjectId](#types-objectid-js) | |
| 563 | * - [Buffer](#types-buffer-js) | |
| 564 | * - [SubDocument](#types-embedded-js) | |
| 565 | * - [Array](#types-array-js) | |
| 566 | * - [DocumentArray](#types-documentarray-js) | |
| 567 | * | |
| 568 | * Using this exposed access to the `ObjectId` type, we can construct ids on demand. | |
| 569 | * | |
| 570 | * var ObjectId = mongoose.Types.ObjectId; | |
| 571 | * var id1 = new ObjectId; | |
| 572 | * | |
| 573 | * @property Types | |
| 574 | * @api public | |
| 575 | */ | |
| 576 | ||
| 577 | 1 | Mongoose.prototype.Types = Types; |
| 578 | ||
| 579 | /** | |
| 580 | * The Mongoose [Query](#query_Query) constructor. | |
| 581 | * | |
| 582 | * @method Query | |
| 583 | * @api public | |
| 584 | */ | |
| 585 | ||
| 586 | 1 | Mongoose.prototype.Query = Query; |
| 587 | ||
| 588 | /** | |
| 589 | * The Mongoose [Promise](#promise_Promise) constructor. | |
| 590 | * | |
| 591 | * @method Promise | |
| 592 | * @api public | |
| 593 | */ | |
| 594 | ||
| 595 | 1 | Mongoose.prototype.Promise = Promise; |
| 596 | ||
| 597 | /** | |
| 598 | * The Mongoose [Model](#model_Model) constructor. | |
| 599 | * | |
| 600 | * @method Model | |
| 601 | * @api public | |
| 602 | */ | |
| 603 | ||
| 604 | 1 | Mongoose.prototype.Model = Model; |
| 605 | ||
| 606 | /** | |
| 607 | * The Mongoose [Document](#document-js) constructor. | |
| 608 | * | |
| 609 | * @method Document | |
| 610 | * @api public | |
| 611 | */ | |
| 612 | ||
| 613 | 1 | Mongoose.prototype.Document = Document; |
| 614 | ||
| 615 | /** | |
| 616 | * The [MongooseError](#error_MongooseError) constructor. | |
| 617 | * | |
| 618 | * @method Error | |
| 619 | * @api public | |
| 620 | */ | |
| 621 | ||
| 622 | 1 | Mongoose.prototype.Error = require('./error'); |
| 623 | ||
| 624 | /** | |
| 625 | * The [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver Mongoose uses. | |
| 626 | * | |
| 627 | * @property mongo | |
| 628 | * @api public | |
| 629 | */ | |
| 630 | ||
| 631 | 1 | Mongoose.prototype.mongo = require('mongodb'); |
| 632 | ||
| 633 | /** | |
| 634 | * The [mquery](https://github.com/aheckmann/mquery) query builder Mongoose uses. | |
| 635 | * | |
| 636 | * @property mquery | |
| 637 | * @api public | |
| 638 | */ | |
| 639 | ||
| 640 | 1 | Mongoose.prototype.mquery = require('mquery'); |
| 641 | ||
| 642 | /*! | |
| 643 | * The exports object is an instance of Mongoose. | |
| 644 | * | |
| 645 | * @api public | |
| 646 | */ | |
| 647 | ||
| 648 | 1 | var mongoose = module.exports = exports = new Mongoose; |
| 649 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Dependencies | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var StateMachine = require('./statemachine') |
| 6 | 1 | var ActiveRoster = StateMachine.ctor('require', 'modify', 'init', 'default') |
| 7 | ||
| 8 | 1 | module.exports = exports = InternalCache; |
| 9 | ||
| 10 | 1 | function InternalCache () { |
| 11 | 0 | this.strictMode = undefined; |
| 12 | 0 | this.selected = undefined; |
| 13 | 0 | this.shardval = undefined; |
| 14 | 0 | this.saveError = undefined; |
| 15 | 0 | this.validationError = undefined; |
| 16 | 0 | this.adhocPaths = undefined; |
| 17 | 0 | this.removing = undefined; |
| 18 | 0 | this.inserting = undefined; |
| 19 | 0 | this.version = undefined; |
| 20 | 0 | this.getters = {}; |
| 21 | 0 | this._id = undefined; |
| 22 | 0 | this.populate = undefined; // what we want to populate in this doc |
| 23 | 0 | this.populated = undefined;// the _ids that have been populated |
| 24 | 0 | this.wasPopulated = false; // if this doc was the result of a population |
| 25 | 0 | this.scope = undefined; |
| 26 | 0 | this.activePaths = new ActiveRoster; |
| 27 | ||
| 28 | // embedded docs | |
| 29 | 0 | this.ownerDocument = undefined; |
| 30 | 0 | this.fullPath = undefined; |
| 31 | } | |
| 32 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Document = require('./document') |
| 6 | , MongooseArray = require('./types/array') | |
| 7 | , MongooseBuffer = require('./types/buffer') | |
| 8 | , MongooseError = require('./error') | |
| 9 | , VersionError = MongooseError.VersionError | |
| 10 | , DivergentArrayError = MongooseError.DivergentArrayError | |
| 11 | , Query = require('./query') | |
| 12 | , Aggregate = require('./aggregate') | |
| 13 | , Schema = require('./schema') | |
| 14 | , Types = require('./schema/index') | |
| 15 | , utils = require('./utils') | |
| 16 | , hasOwnProperty = utils.object.hasOwnProperty | |
| 17 | , isMongooseObject = utils.isMongooseObject | |
| 18 | , EventEmitter = require('events').EventEmitter | |
| 19 | , merge = utils.merge | |
| 20 | , Promise = require('./promise') | |
| 21 | , assert = require('assert') | |
| 22 | , util = require('util') | |
| 23 | , tick = utils.tick | |
| 24 | , Query = require('./query.js') | |
| 25 | ||
| 26 | 1 | var VERSION_WHERE = 1 |
| 27 | , VERSION_INC = 2 | |
| 28 | , VERSION_ALL = VERSION_WHERE | VERSION_INC; | |
| 29 | ||
| 30 | /** | |
| 31 | * Model constructor | |
| 32 | * | |
| 33 | * Provides the interface to MongoDB collections as well as creates document instances. | |
| 34 | * | |
| 35 | * @param {Object} doc values with which to create the document | |
| 36 | * @inherits Document | |
| 37 | * @event `error`: If listening to this event, it is emitted when a document was saved without passing a callback and an `error` occurred. If not listening, the event bubbles to the connection used to create this Model. | |
| 38 | * @event `index`: Emitted after `Model#ensureIndexes` completes. If an error occurred it is passed with the event. | |
| 39 | * @api public | |
| 40 | */ | |
| 41 | ||
| 42 | 1 | function Model (doc, fields, skipId) { |
| 43 | 0 | Document.call(this, doc, fields, skipId); |
| 44 | }; | |
| 45 | ||
| 46 | /*! | |
| 47 | * Inherits from Document. | |
| 48 | * | |
| 49 | * All Model.prototype features are available on | |
| 50 | * top level (non-sub) documents. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | Model.prototype.__proto__ = Document.prototype; |
| 54 | ||
| 55 | /** | |
| 56 | * Connection the model uses. | |
| 57 | * | |
| 58 | * @api public | |
| 59 | * @property db | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | Model.prototype.db; |
| 63 | ||
| 64 | /** | |
| 65 | * Collection the model uses. | |
| 66 | * | |
| 67 | * @api public | |
| 68 | * @property collection | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | Model.prototype.collection; |
| 72 | ||
| 73 | /** | |
| 74 | * The name of the model | |
| 75 | * | |
| 76 | * @api public | |
| 77 | * @property modelName | |
| 78 | */ | |
| 79 | ||
| 80 | 1 | Model.prototype.modelName; |
| 81 | ||
| 82 | /*! | |
| 83 | * Handles doc.save() callbacks | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | function handleSave (promise, self) { |
| 87 | 0 | return tick(function handleSave (err, result) { |
| 88 | 0 | if (err) { |
| 89 | // If the initial insert fails provide a second chance. | |
| 90 | // (If we did this all the time we would break updates) | |
| 91 | 0 | if (self.$__.inserting) { |
| 92 | 0 | self.isNew = true; |
| 93 | 0 | self.emit('isNew', true); |
| 94 | } | |
| 95 | 0 | promise.error(err); |
| 96 | 0 | promise = self = null; |
| 97 | 0 | return; |
| 98 | } | |
| 99 | ||
| 100 | 0 | self.$__storeShard(); |
| 101 | ||
| 102 | 0 | var numAffected; |
| 103 | 0 | if (result) { |
| 104 | // when inserting, the array of created docs is returned | |
| 105 | 0 | numAffected = result.length |
| 106 | ? result.length | |
| 107 | : result; | |
| 108 | } else { | |
| 109 | 0 | numAffected = 0; |
| 110 | } | |
| 111 | ||
| 112 | // was this an update that required a version bump? | |
| 113 | 0 | if (self.$__.version && !self.$__.inserting) { |
| 114 | 0 | var doIncrement = VERSION_INC === (VERSION_INC & self.$__.version); |
| 115 | 0 | self.$__.version = undefined; |
| 116 | ||
| 117 | // increment version if was successful | |
| 118 | 0 | if (numAffected > 0) { |
| 119 | 0 | if (doIncrement) { |
| 120 | 0 | var key = self.schema.options.versionKey; |
| 121 | 0 | var version = self.getValue(key) | 0; |
| 122 | 0 | self.setValue(key, version + 1); |
| 123 | } | |
| 124 | } else { | |
| 125 | // the update failed. pass an error back | |
| 126 | 0 | promise.error(new VersionError); |
| 127 | 0 | promise = self = null; |
| 128 | 0 | return; |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | 0 | self.emit('save', self, numAffected); |
| 133 | 0 | promise.complete(self, numAffected); |
| 134 | 0 | promise = self = null; |
| 135 | }); | |
| 136 | } | |
| 137 | ||
| 138 | /** | |
| 139 | * Saves this document. | |
| 140 | * | |
| 141 | * ####Example: | |
| 142 | * | |
| 143 | * product.sold = Date.now(); | |
| 144 | * product.save(function (err, product, numberAffected) { | |
| 145 | * if (err) .. | |
| 146 | * }) | |
| 147 | * | |
| 148 | * The callback will receive three parameters, `err` if an error occurred, `product` which is the saved `product`, and `numberAffected` which will be 1 when the document was found and updated in the database, otherwise 0. | |
| 149 | * | |
| 150 | * The `fn` callback is optional. If no `fn` is passed and validation fails, the validation error will be emitted on the connection used to create this model. | |
| 151 | * | |
| 152 | * var db = mongoose.createConnection(..); | |
| 153 | * var schema = new Schema(..); | |
| 154 | * var Product = db.model('Product', schema); | |
| 155 | * | |
| 156 | * db.on('error', handleError); | |
| 157 | * | |
| 158 | * However, if you desire more local error handling you can add an `error` listener to the model and handle errors there instead. | |
| 159 | * | |
| 160 | * Product.on('error', handleError); | |
| 161 | * | |
| 162 | * @param {Function} [fn] optional callback | |
| 163 | * @api public | |
| 164 | * @see middleware http://mongoosejs.com/docs/middleware.html | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | Model.prototype.save = function save (fn) { |
| 168 | 0 | var promise = new Promise(fn) |
| 169 | , complete = handleSave(promise, this) | |
| 170 | , options = {} | |
| 171 | ||
| 172 | 0 | if (this.schema.options.safe) { |
| 173 | 0 | options.safe = this.schema.options.safe; |
| 174 | } | |
| 175 | ||
| 176 | 0 | if (this.isNew) { |
| 177 | // send entire doc | |
| 178 | 0 | var obj = this.toObject({ depopulate: 1 }); |
| 179 | ||
| 180 | 0 | if (!utils.object.hasOwnProperty(obj || {}, '_id')) { |
| 181 | // documents must have an _id else mongoose won't know | |
| 182 | // what to update later if more changes are made. the user | |
| 183 | // wouldn't know what _id was generated by mongodb either | |
| 184 | // nor would the ObjectId generated my mongodb necessarily | |
| 185 | // match the schema definition. | |
| 186 | 0 | return complete(new Error('document must have an _id before saving')); |
| 187 | } | |
| 188 | ||
| 189 | 0 | this.$__version(true, obj); |
| 190 | 0 | this.collection.insert(obj, options, complete); |
| 191 | 0 | this.$__reset(); |
| 192 | 0 | this.isNew = false; |
| 193 | 0 | this.emit('isNew', false); |
| 194 | // Make it possible to retry the insert | |
| 195 | 0 | this.$__.inserting = true; |
| 196 | ||
| 197 | } else { | |
| 198 | // Make sure we don't treat it as a new object on error, | |
| 199 | // since it already exists | |
| 200 | 0 | this.$__.inserting = false; |
| 201 | ||
| 202 | 0 | var delta = this.$__delta(); |
| 203 | ||
| 204 | 0 | if (delta) { |
| 205 | 0 | if (delta instanceof Error) return complete(delta); |
| 206 | 0 | var where = this.$__where(delta[0]); |
| 207 | 0 | this.$__reset(); |
| 208 | 0 | this.collection.update(where, delta[1], options, complete); |
| 209 | } else { | |
| 210 | 0 | this.$__reset(); |
| 211 | 0 | complete(null); |
| 212 | } | |
| 213 | ||
| 214 | 0 | this.emit('isNew', false); |
| 215 | } | |
| 216 | }; | |
| 217 | ||
| 218 | /*! | |
| 219 | * Apply the operation to the delta (update) clause as | |
| 220 | * well as track versioning for our where clause. | |
| 221 | * | |
| 222 | * @param {Document} self | |
| 223 | * @param {Object} where | |
| 224 | * @param {Object} delta | |
| 225 | * @param {Object} data | |
| 226 | * @param {Mixed} val | |
| 227 | * @param {String} [operation] | |
| 228 | */ | |
| 229 | ||
| 230 | 1 | function operand (self, where, delta, data, val, op) { |
| 231 | // delta | |
| 232 | 0 | op || (op = '$set'); |
| 233 | 0 | if (!delta[op]) delta[op] = {}; |
| 234 | 0 | delta[op][data.path] = val; |
| 235 | ||
| 236 | // disabled versioning? | |
| 237 | 0 | if (false === self.schema.options.versionKey) return; |
| 238 | ||
| 239 | // already marked for versioning? | |
| 240 | 0 | if (VERSION_ALL === (VERSION_ALL & self.$__.version)) return; |
| 241 | ||
| 242 | 0 | switch (op) { |
| 243 | case '$set': | |
| 244 | case '$unset': | |
| 245 | case '$pop': | |
| 246 | case '$pull': | |
| 247 | case '$pullAll': | |
| 248 | case '$push': | |
| 249 | case '$pushAll': | |
| 250 | case '$addToSet': | |
| 251 | 0 | break; |
| 252 | default: | |
| 253 | // nothing to do | |
| 254 | 0 | return; |
| 255 | } | |
| 256 | ||
| 257 | // ensure updates sent with positional notation are | |
| 258 | // editing the correct array element. | |
| 259 | // only increment the version if an array position changes. | |
| 260 | // modifying elements of an array is ok if position does not change. | |
| 261 | ||
| 262 | 0 | if ('$push' == op || '$pushAll' == op || '$addToSet' == op) { |
| 263 | 0 | self.$__.version = VERSION_INC; |
| 264 | } | |
| 265 | 0 | else if (/^\$p/.test(op)) { |
| 266 | // potentially changing array positions | |
| 267 | 0 | self.increment(); |
| 268 | } | |
| 269 | 0 | else if (Array.isArray(val)) { |
| 270 | // $set an array | |
| 271 | 0 | self.increment(); |
| 272 | } | |
| 273 | // now handling $set, $unset | |
| 274 | 0 | else if (/\.\d+\.|\.\d+$/.test(data.path)) { |
| 275 | // subpath of array | |
| 276 | 0 | self.$__.version = VERSION_WHERE; |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | /*! | |
| 281 | * Compiles an update and where clause for a `val` with _atomics. | |
| 282 | * | |
| 283 | * @param {Document} self | |
| 284 | * @param {Object} where | |
| 285 | * @param {Object} delta | |
| 286 | * @param {Object} data | |
| 287 | * @param {Array} value | |
| 288 | */ | |
| 289 | ||
| 290 | 1 | function handleAtomics (self, where, delta, data, value) { |
| 291 | 0 | if (delta.$set && delta.$set[data.path]) { |
| 292 | // $set has precedence over other atomics | |
| 293 | 0 | return; |
| 294 | } | |
| 295 | ||
| 296 | 0 | if ('function' == typeof value.$__getAtomics) { |
| 297 | 0 | value.$__getAtomics().forEach(function (atomic) { |
| 298 | 0 | var op = atomic[0]; |
| 299 | 0 | var val = atomic[1]; |
| 300 | 0 | operand(self, where, delta, data, val, op); |
| 301 | }) | |
| 302 | 0 | return; |
| 303 | } | |
| 304 | ||
| 305 | // legacy support for plugins | |
| 306 | ||
| 307 | 0 | var atomics = value._atomics |
| 308 | , ops = Object.keys(atomics) | |
| 309 | , i = ops.length | |
| 310 | , val | |
| 311 | , op; | |
| 312 | ||
| 313 | 0 | if (0 === i) { |
| 314 | // $set | |
| 315 | ||
| 316 | 0 | if (isMongooseObject(value)) { |
| 317 | 0 | value = value.toObject({ depopulate: 1 }); |
| 318 | 0 | } else if (value.valueOf) { |
| 319 | 0 | value = value.valueOf(); |
| 320 | } | |
| 321 | ||
| 322 | 0 | return operand(self, where, delta, data, value); |
| 323 | } | |
| 324 | ||
| 325 | 0 | while (i--) { |
| 326 | 0 | op = ops[i]; |
| 327 | 0 | val = atomics[op]; |
| 328 | ||
| 329 | 0 | if (isMongooseObject(val)) { |
| 330 | 0 | val = val.toObject({ depopulate: 1 }) |
| 331 | 0 | } else if (Array.isArray(val)) { |
| 332 | 0 | val = val.map(function (mem) { |
| 333 | 0 | return isMongooseObject(mem) |
| 334 | ? mem.toObject({ depopulate: 1 }) | |
| 335 | : mem; | |
| 336 | }) | |
| 337 | 0 | } else if (val.valueOf) { |
| 338 | 0 | val = val.valueOf() |
| 339 | } | |
| 340 | ||
| 341 | 0 | if ('$addToSet' === op) |
| 342 | 0 | val = { $each: val }; |
| 343 | ||
| 344 | 0 | operand(self, where, delta, data, val, op); |
| 345 | } | |
| 346 | } | |
| 347 | ||
| 348 | /** | |
| 349 | * Produces a special query document of the modified properties used in updates. | |
| 350 | * | |
| 351 | * @api private | |
| 352 | * @method $__delta | |
| 353 | * @memberOf Model | |
| 354 | */ | |
| 355 | ||
| 356 | 1 | Model.prototype.$__delta = function () { |
| 357 | 0 | var dirty = this.$__dirty(); |
| 358 | 0 | if (!dirty.length && VERSION_ALL != this.$__.version) return; |
| 359 | ||
| 360 | 0 | var where = {} |
| 361 | , delta = {} | |
| 362 | , len = dirty.length | |
| 363 | , divergent = [] | |
| 364 | , d = 0 | |
| 365 | , val | |
| 366 | , obj | |
| 367 | ||
| 368 | 0 | for (; d < len; ++d) { |
| 369 | 0 | var data = dirty[d] |
| 370 | 0 | var value = data.value |
| 371 | 0 | var schema = data.schema |
| 372 | ||
| 373 | 0 | var match = checkDivergentArray(this, data.path, value); |
| 374 | 0 | if (match) { |
| 375 | 0 | divergent.push(match); |
| 376 | 0 | continue; |
| 377 | } | |
| 378 | ||
| 379 | 0 | if (divergent.length) continue; |
| 380 | ||
| 381 | 0 | if (undefined === value) { |
| 382 | 0 | operand(this, where, delta, data, 1, '$unset'); |
| 383 | ||
| 384 | 0 | } else if (null === value) { |
| 385 | 0 | operand(this, where, delta, data, null); |
| 386 | ||
| 387 | 0 | } else if (value._path && value._atomics) { |
| 388 | // arrays and other custom types (support plugins etc) | |
| 389 | 0 | handleAtomics(this, where, delta, data, value); |
| 390 | ||
| 391 | 0 | } else if (value._path && Buffer.isBuffer(value)) { |
| 392 | // MongooseBuffer | |
| 393 | 0 | value = value.toObject(); |
| 394 | 0 | operand(this, where, delta, data, value); |
| 395 | ||
| 396 | } else { | |
| 397 | 0 | value = utils.clone(value, { depopulate: 1 }); |
| 398 | 0 | operand(this, where, delta, data, value); |
| 399 | } | |
| 400 | } | |
| 401 | ||
| 402 | 0 | if (divergent.length) { |
| 403 | 0 | return new DivergentArrayError(divergent); |
| 404 | } | |
| 405 | ||
| 406 | 0 | if (this.$__.version) { |
| 407 | 0 | this.$__version(where, delta); |
| 408 | } | |
| 409 | ||
| 410 | 0 | return [where, delta]; |
| 411 | } | |
| 412 | ||
| 413 | /*! | |
| 414 | * Determine if array was populated with some form of filter and is now | |
| 415 | * being updated in a manner which could overwrite data unintentionally. | |
| 416 | * | |
| 417 | * @see https://github.com/LearnBoost/mongoose/issues/1334 | |
| 418 | * @param {Document} doc | |
| 419 | * @param {String} path | |
| 420 | * @return {String|undefined} | |
| 421 | */ | |
| 422 | ||
| 423 | 1 | function checkDivergentArray (doc, path, array) { |
| 424 | // see if we populated this path | |
| 425 | 0 | var pop = doc.populated(path, true); |
| 426 | ||
| 427 | 0 | if (!pop && doc.$__.selected) { |
| 428 | // If any array was selected using an $elemMatch projection, we deny the update. | |
| 429 | // NOTE: MongoDB only supports projected $elemMatch on top level array. | |
| 430 | 0 | var top = path.split('.')[0]; |
| 431 | 0 | if (doc.$__.selected[top] && doc.$__.selected[top].$elemMatch) { |
| 432 | 0 | return top; |
| 433 | } | |
| 434 | } | |
| 435 | ||
| 436 | 0 | if (!(pop && array instanceof MongooseArray)) return; |
| 437 | ||
| 438 | // If the array was populated using options that prevented all | |
| 439 | // documents from being returned (match, skip, limit) or they | |
| 440 | // deselected the _id field, $pop and $set of the array are | |
| 441 | // not safe operations. If _id was deselected, we do not know | |
| 442 | // how to remove elements. $pop will pop off the _id from the end | |
| 443 | // of the array in the db which is not guaranteed to be the | |
| 444 | // same as the last element we have here. $set of the entire array | |
| 445 | // would be similarily destructive as we never received all | |
| 446 | // elements of the array and potentially would overwrite data. | |
| 447 | 0 | var check = pop.options.match || |
| 448 | pop.options.options && hasOwnProperty(pop.options.options, 'limit') || // 0 is not permitted | |
| 449 | pop.options.options && pop.options.options.skip || // 0 is permitted | |
| 450 | pop.options.select && // deselected _id? | |
| 451 | (0 === pop.options.select._id || | |
| 452 | /\s?-_id\s?/.test(pop.options.select)) | |
| 453 | ||
| 454 | 0 | if (check) { |
| 455 | 0 | var atomics = array._atomics; |
| 456 | 0 | if (0 === Object.keys(atomics).length || atomics.$set || atomics.$pop) { |
| 457 | 0 | return path; |
| 458 | } | |
| 459 | } | |
| 460 | } | |
| 461 | ||
| 462 | /** | |
| 463 | * Appends versioning to the where and update clauses. | |
| 464 | * | |
| 465 | * @api private | |
| 466 | * @method $__version | |
| 467 | * @memberOf Model | |
| 468 | */ | |
| 469 | ||
| 470 | 1 | Model.prototype.$__version = function (where, delta) { |
| 471 | 0 | var key = this.schema.options.versionKey; |
| 472 | ||
| 473 | 0 | if (true === where) { |
| 474 | // this is an insert | |
| 475 | 0 | if (key) this.setValue(key, delta[key] = 0); |
| 476 | 0 | return; |
| 477 | } | |
| 478 | ||
| 479 | // updates | |
| 480 | ||
| 481 | // only apply versioning if our versionKey was selected. else | |
| 482 | // there is no way to select the correct version. we could fail | |
| 483 | // fast here and force them to include the versionKey but | |
| 484 | // thats a bit intrusive. can we do this automatically? | |
| 485 | 0 | if (!this.isSelected(key)) { |
| 486 | 0 | return; |
| 487 | } | |
| 488 | ||
| 489 | // $push $addToSet don't need the where clause set | |
| 490 | 0 | if (VERSION_WHERE === (VERSION_WHERE & this.$__.version)) { |
| 491 | 0 | where[key] = this.getValue(key); |
| 492 | } | |
| 493 | ||
| 494 | 0 | if (VERSION_INC === (VERSION_INC & this.$__.version)) { |
| 495 | 0 | delta.$inc || (delta.$inc = {}); |
| 496 | 0 | delta.$inc[key] = 1; |
| 497 | } | |
| 498 | } | |
| 499 | ||
| 500 | /** | |
| 501 | * Signal that we desire an increment of this documents version. | |
| 502 | * | |
| 503 | * ####Example: | |
| 504 | * | |
| 505 | * Model.findById(id, function (err, doc) { | |
| 506 | * doc.increment(); | |
| 507 | * doc.save(function (err) { .. }) | |
| 508 | * }) | |
| 509 | * | |
| 510 | * @see versionKeys http://mongoosejs.com/docs/guide.html#versionKey | |
| 511 | * @api public | |
| 512 | */ | |
| 513 | ||
| 514 | 1 | Model.prototype.increment = function increment () { |
| 515 | 0 | this.$__.version = VERSION_ALL; |
| 516 | 0 | return this; |
| 517 | } | |
| 518 | ||
| 519 | /** | |
| 520 | * Returns a query object which applies shardkeys if they exist. | |
| 521 | * | |
| 522 | * @api private | |
| 523 | * @method $__where | |
| 524 | * @memberOf Model | |
| 525 | */ | |
| 526 | ||
| 527 | 1 | Model.prototype.$__where = function _where (where) { |
| 528 | 0 | where || (where = {}); |
| 529 | ||
| 530 | 0 | var paths |
| 531 | , len | |
| 532 | ||
| 533 | 0 | if (this.$__.shardval) { |
| 534 | 0 | paths = Object.keys(this.$__.shardval) |
| 535 | 0 | len = paths.length |
| 536 | ||
| 537 | 0 | for (var i = 0; i < len; ++i) { |
| 538 | 0 | where[paths[i]] = this.$__.shardval[paths[i]]; |
| 539 | } | |
| 540 | } | |
| 541 | ||
| 542 | 0 | where._id = this._doc._id; |
| 543 | 0 | return where; |
| 544 | } | |
| 545 | ||
| 546 | /** | |
| 547 | * Removes this document from the db. | |
| 548 | * | |
| 549 | * ####Example: | |
| 550 | * | |
| 551 | * product.remove(function (err, product) { | |
| 552 | * if (err) return handleError(err); | |
| 553 | * Product.findById(product._id, function (err, product) { | |
| 554 | * console.log(product) // null | |
| 555 | * }) | |
| 556 | * }) | |
| 557 | * | |
| 558 | * @param {Function} [fn] optional callback | |
| 559 | * @api public | |
| 560 | */ | |
| 561 | ||
| 562 | 1 | Model.prototype.remove = function remove (fn) { |
| 563 | 0 | if (this.$__.removing) { |
| 564 | 0 | this.$__.removing.addBack(fn); |
| 565 | 0 | return this; |
| 566 | } | |
| 567 | ||
| 568 | 0 | var promise = this.$__.removing = new Promise(fn) |
| 569 | , where = this.$__where() | |
| 570 | , self = this | |
| 571 | , options = {} | |
| 572 | ||
| 573 | 0 | if (this.schema.options.safe) { |
| 574 | 0 | options.safe = this.schema.options.safe; |
| 575 | } | |
| 576 | ||
| 577 | 0 | this.collection.remove(where, options, tick(function (err) { |
| 578 | 0 | if (err) { |
| 579 | 0 | promise.error(err); |
| 580 | 0 | promise = self = self.$__.removing = where = options = null; |
| 581 | 0 | return; |
| 582 | } | |
| 583 | 0 | self.emit('remove', self); |
| 584 | 0 | promise.complete(self); |
| 585 | 0 | promise = self = where = options = null; |
| 586 | })); | |
| 587 | ||
| 588 | 0 | return this; |
| 589 | }; | |
| 590 | ||
| 591 | /** | |
| 592 | * Returns another Model instance. | |
| 593 | * | |
| 594 | * ####Example: | |
| 595 | * | |
| 596 | * var doc = new Tank; | |
| 597 | * doc.model('User').findById(id, callback); | |
| 598 | * | |
| 599 | * @param {String} name model name | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | ||
| 603 | 1 | Model.prototype.model = function model (name) { |
| 604 | 0 | return this.db.model(name); |
| 605 | }; | |
| 606 | ||
| 607 | /** | |
| 608 | * Adds a discriminator type. | |
| 609 | * | |
| 610 | * ####Example: | |
| 611 | * | |
| 612 | * function BaseSchema() { | |
| 613 | * Schema.apply(this, arguments); | |
| 614 | * | |
| 615 | * this.add({ | |
| 616 | * name: String, | |
| 617 | * createdAt: Date | |
| 618 | * }); | |
| 619 | * } | |
| 620 | * util.inherits(BaseSchema, Schema); | |
| 621 | * | |
| 622 | * var PersonSchema = new BaseSchema(); | |
| 623 | * var BossSchema = new BaseSchema({ department: String }); | |
| 624 | * | |
| 625 | * var Person = mongoose.model('Person', PersonSchema); | |
| 626 | * var Boss = Person.discriminator('Boss', BossSchema); | |
| 627 | * | |
| 628 | * @param {String} name discriminator model name | |
| 629 | * @param {Schema} schema discriminator model schema | |
| 630 | * @api public | |
| 631 | */ | |
| 632 | ||
| 633 | 1 | Model.discriminator = function discriminator (name, schema) { |
| 634 | 0 | if (!(schema instanceof Schema)) { |
| 635 | 0 | throw new Error("You must pass a valid discriminator Schema"); |
| 636 | } | |
| 637 | ||
| 638 | 0 | if (this.schema.discriminatorMapping && !this.schema.discriminatorMapping.isRoot) { |
| 639 | 0 | throw new Error("Discriminator \"" + name + "\" can only be a discriminator of the root model"); |
| 640 | } | |
| 641 | ||
| 642 | 0 | var key = this.schema.options.discriminatorKey; |
| 643 | 0 | if (schema.path(key)) { |
| 644 | 0 | throw new Error("Discriminator \"" + name + "\" cannot have field with name \"" + key + "\""); |
| 645 | } | |
| 646 | ||
| 647 | // merges base schema into new discriminator schema and sets new type field. | |
| 648 | 0 | (function mergeSchemas(schema, baseSchema) { |
| 649 | 0 | utils.merge(schema, baseSchema); |
| 650 | ||
| 651 | 0 | var obj = {}; |
| 652 | 0 | obj[key] = { type: String, default: name }; |
| 653 | 0 | schema.add(obj); |
| 654 | 0 | schema.discriminatorMapping = { key: key, value: name, isRoot: false }; |
| 655 | ||
| 656 | 0 | if (baseSchema.options.collection) { |
| 657 | 0 | schema.options.collection = baseSchema.options.collection; |
| 658 | } | |
| 659 | ||
| 660 | // throws error if options are invalid | |
| 661 | 0 | (function validateOptions(a, b) { |
| 662 | 0 | a = utils.clone(a); |
| 663 | 0 | b = utils.clone(b); |
| 664 | 0 | delete a.toJSON; |
| 665 | 0 | delete a.toObject; |
| 666 | 0 | delete b.toJSON; |
| 667 | 0 | delete b.toObject; |
| 668 | ||
| 669 | 0 | if (!utils.deepEqual(a, b)) { |
| 670 | 0 | throw new Error("Discriminator options are not customizable (except toJSON & toObject)"); |
| 671 | } | |
| 672 | })(schema.options, baseSchema.options); | |
| 673 | ||
| 674 | 0 | var toJSON = schema.options.toJSON |
| 675 | , toObject = schema.options.toObject; | |
| 676 | ||
| 677 | 0 | schema.options = utils.clone(baseSchema.options); |
| 678 | 0 | if (toJSON) schema.options.toJSON = toJSON; |
| 679 | 0 | if (toObject) schema.options.toObject = toObject; |
| 680 | ||
| 681 | 0 | schema.callQueue = baseSchema.callQueue.concat(schema.callQueue); |
| 682 | 0 | schema._requiredpaths = undefined; // reset just in case Schema#requiredPaths() was called on either schema |
| 683 | })(schema, this.schema); | |
| 684 | ||
| 685 | 0 | if (!this.discriminators) { |
| 686 | 0 | this.discriminators = {}; |
| 687 | } | |
| 688 | ||
| 689 | 0 | if (!this.schema.discriminatorMapping) { |
| 690 | 0 | this.schema.discriminatorMapping = { key: key, value: null, isRoot: true }; |
| 691 | } | |
| 692 | ||
| 693 | 0 | if (this.discriminators[name]) { |
| 694 | 0 | throw new Error("Discriminator with name \"" + name + "\" already exists"); |
| 695 | } | |
| 696 | ||
| 697 | 0 | this.discriminators[name] = this.db.model(name, schema, this.collection.name); |
| 698 | 0 | this.discriminators[name].prototype.__proto__ = this.prototype; |
| 699 | ||
| 700 | 0 | return this.discriminators[name]; |
| 701 | }; | |
| 702 | ||
| 703 | // Model (class) features | |
| 704 | ||
| 705 | /*! | |
| 706 | * Give the constructor the ability to emit events. | |
| 707 | */ | |
| 708 | ||
| 709 | 1 | for (var i in EventEmitter.prototype) |
| 710 | 8 | Model[i] = EventEmitter.prototype[i]; |
| 711 | ||
| 712 | /** | |
| 713 | * Called when the model compiles. | |
| 714 | * | |
| 715 | * @api private | |
| 716 | */ | |
| 717 | ||
| 718 | 1 | Model.init = function init () { |
| 719 | 0 | if (this.schema.options.autoIndex) { |
| 720 | 0 | this.ensureIndexes(); |
| 721 | } | |
| 722 | ||
| 723 | 0 | this.schema.emit('init', this); |
| 724 | }; | |
| 725 | ||
| 726 | /** | |
| 727 | * Sends `ensureIndex` commands to mongo for each index declared in the schema. | |
| 728 | * | |
| 729 | * ####Example: | |
| 730 | * | |
| 731 | * Event.ensureIndexes(function (err) { | |
| 732 | * if (err) return handleError(err); | |
| 733 | * }); | |
| 734 | * | |
| 735 | * After completion, an `index` event is emitted on this `Model` passing an error if one occurred. | |
| 736 | * | |
| 737 | * ####Example: | |
| 738 | * | |
| 739 | * var eventSchema = new Schema({ thing: { type: 'string', unique: true }}) | |
| 740 | * var Event = mongoose.model('Event', eventSchema); | |
| 741 | * | |
| 742 | * Event.on('index', function (err) { | |
| 743 | * if (err) console.error(err); // error occurred during index creation | |
| 744 | * }) | |
| 745 | * | |
| 746 | * _NOTE: It is not recommended that you run this in production. Index creation may impact database performance depending on your load. Use with caution._ | |
| 747 | * | |
| 748 | * The `ensureIndex` commands are not sent in parallel. This is to avoid the `MongoError: cannot add index with a background operation in progress` error. See [this ticket](https://github.com/LearnBoost/mongoose/issues/1365) for more information. | |
| 749 | * | |
| 750 | * @param {Function} [cb] optional callback | |
| 751 | * @return {Promise} | |
| 752 | * @api public | |
| 753 | */ | |
| 754 | ||
| 755 | 1 | Model.ensureIndexes = function ensureIndexes (cb) { |
| 756 | 0 | var promise = new Promise(cb); |
| 757 | ||
| 758 | 0 | var indexes = this.schema.indexes(); |
| 759 | 0 | if (!indexes.length) { |
| 760 | 0 | process.nextTick(promise.fulfill.bind(promise)); |
| 761 | 0 | return promise; |
| 762 | } | |
| 763 | ||
| 764 | // Indexes are created one-by-one to support how MongoDB < 2.4 deals | |
| 765 | // with background indexes. | |
| 766 | ||
| 767 | 0 | var self = this |
| 768 | , safe = self.schema.options.safe | |
| 769 | ||
| 770 | 0 | function done (err) { |
| 771 | 0 | self.emit('index', err); |
| 772 | 0 | promise.resolve(err); |
| 773 | } | |
| 774 | ||
| 775 | 0 | function create () { |
| 776 | 0 | var index = indexes.shift(); |
| 777 | 0 | if (!index) return done(); |
| 778 | ||
| 779 | 0 | var options = index[1]; |
| 780 | 0 | options.safe = safe; |
| 781 | 0 | self.collection.ensureIndex(index[0], options, tick(function (err) { |
| 782 | 0 | if (err) return done(err); |
| 783 | 0 | create(); |
| 784 | })); | |
| 785 | } | |
| 786 | ||
| 787 | 0 | create(); |
| 788 | 0 | return promise; |
| 789 | } | |
| 790 | ||
| 791 | /** | |
| 792 | * Schema the model uses. | |
| 793 | * | |
| 794 | * @property schema | |
| 795 | * @receiver Model | |
| 796 | * @api public | |
| 797 | */ | |
| 798 | ||
| 799 | 1 | Model.schema; |
| 800 | ||
| 801 | /*! | |
| 802 | * Connection instance the model uses. | |
| 803 | * | |
| 804 | * @property db | |
| 805 | * @receiver Model | |
| 806 | * @api public | |
| 807 | */ | |
| 808 | ||
| 809 | 1 | Model.db; |
| 810 | ||
| 811 | /*! | |
| 812 | * Collection the model uses. | |
| 813 | * | |
| 814 | * @property collection | |
| 815 | * @receiver Model | |
| 816 | * @api public | |
| 817 | */ | |
| 818 | ||
| 819 | 1 | Model.collection; |
| 820 | ||
| 821 | /** | |
| 822 | * Base Mongoose instance the model uses. | |
| 823 | * | |
| 824 | * @property base | |
| 825 | * @receiver Model | |
| 826 | * @api public | |
| 827 | */ | |
| 828 | ||
| 829 | 1 | Model.base; |
| 830 | ||
| 831 | /** | |
| 832 | * Registered discriminators for this model. | |
| 833 | * | |
| 834 | * @property discriminators | |
| 835 | * @receiver Model | |
| 836 | * @api public | |
| 837 | */ | |
| 838 | ||
| 839 | 1 | Model.discriminators; |
| 840 | ||
| 841 | /** | |
| 842 | * Removes documents from the collection. | |
| 843 | * | |
| 844 | * ####Example: | |
| 845 | * | |
| 846 | * Comment.remove({ title: 'baby born from alien father' }, function (err) { | |
| 847 | * | |
| 848 | * }); | |
| 849 | * | |
| 850 | * ####Note: | |
| 851 | * | |
| 852 | * To remove documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js): | |
| 853 | * | |
| 854 | * var query = Comment.remove({ _id: id }); | |
| 855 | * query.exec(); | |
| 856 | * | |
| 857 | * ####Note: | |
| 858 | * | |
| 859 | * This method sends a remove command directly to MongoDB, no Mongoose documents are involved. Because no Mongoose documents are involved, _no middleware (hooks) are executed_. | |
| 860 | * | |
| 861 | * @param {Object} conditions | |
| 862 | * @param {Function} [callback] | |
| 863 | * @return {Query} | |
| 864 | * @api public | |
| 865 | */ | |
| 866 | ||
| 867 | 1 | Model.remove = function remove (conditions, callback) { |
| 868 | 0 | if ('function' === typeof conditions) { |
| 869 | 0 | callback = conditions; |
| 870 | 0 | conditions = {}; |
| 871 | } | |
| 872 | ||
| 873 | // get the mongodb collection object | |
| 874 | 0 | var mq = new Query(conditions, {}, this, this.collection); |
| 875 | ||
| 876 | 0 | return mq.remove(callback); |
| 877 | }; | |
| 878 | ||
| 879 | /** | |
| 880 | * Finds documents | |
| 881 | * | |
| 882 | * The `conditions` are cast to their respective SchemaTypes before the command is sent. | |
| 883 | * | |
| 884 | * ####Examples: | |
| 885 | * | |
| 886 | * // named john and at least 18 | |
| 887 | * MyModel.find({ name: 'john', age: { $gte: 18 }}); | |
| 888 | * | |
| 889 | * // executes immediately, passing results to callback | |
| 890 | * MyModel.find({ name: 'john', age: { $gte: 18 }}, function (err, docs) {}); | |
| 891 | * | |
| 892 | * // name LIKE john and only selecting the "name" and "friends" fields, executing immediately | |
| 893 | * MyModel.find({ name: /john/i }, 'name friends', function (err, docs) { }) | |
| 894 | * | |
| 895 | * // passing options | |
| 896 | * MyModel.find({ name: /john/i }, null, { skip: 10 }) | |
| 897 | * | |
| 898 | * // passing options and executing immediately | |
| 899 | * MyModel.find({ name: /john/i }, null, { skip: 10 }, function (err, docs) {}); | |
| 900 | * | |
| 901 | * // executing a query explicitly | |
| 902 | * var query = MyModel.find({ name: /john/i }, null, { skip: 10 }) | |
| 903 | * query.exec(function (err, docs) {}); | |
| 904 | * | |
| 905 | * // using the promise returned from executing a query | |
| 906 | * var query = MyModel.find({ name: /john/i }, null, { skip: 10 }); | |
| 907 | * var promise = query.exec(); | |
| 908 | * promise.addBack(function (err, docs) {}); | |
| 909 | * | |
| 910 | * @param {Object} conditions | |
| 911 | * @param {Object} [fields] optional fields to select | |
| 912 | * @param {Object} [options] optional | |
| 913 | * @param {Function} [callback] | |
| 914 | * @return {Query} | |
| 915 | * @see field selection #query_Query-select | |
| 916 | * @see promise #promise-js | |
| 917 | * @api public | |
| 918 | */ | |
| 919 | ||
| 920 | 1 | Model.find = function find (conditions, fields, options, callback) { |
| 921 | 0 | if ('function' == typeof conditions) { |
| 922 | 0 | callback = conditions; |
| 923 | 0 | conditions = {}; |
| 924 | 0 | fields = null; |
| 925 | 0 | options = null; |
| 926 | 0 | } else if ('function' == typeof fields) { |
| 927 | 0 | callback = fields; |
| 928 | 0 | fields = null; |
| 929 | 0 | options = null; |
| 930 | 0 | } else if ('function' == typeof options) { |
| 931 | 0 | callback = options; |
| 932 | 0 | options = null; |
| 933 | } | |
| 934 | ||
| 935 | // get the raw mongodb collection object | |
| 936 | 0 | var mq = new Query({}, options, this, this.collection); |
| 937 | 0 | mq.select(fields); |
| 938 | 0 | if (this.schema.discriminatorMapping && mq._selectedInclusively()) { |
| 939 | 0 | mq.select(this.schema.options.discriminatorKey); |
| 940 | } | |
| 941 | ||
| 942 | 0 | return mq.find(conditions, callback); |
| 943 | }; | |
| 944 | ||
| 945 | /** | |
| 946 | * Finds a single document by id. | |
| 947 | * | |
| 948 | * The `id` is cast based on the Schema before sending the command. | |
| 949 | * | |
| 950 | * ####Example: | |
| 951 | * | |
| 952 | * // find adventure by id and execute immediately | |
| 953 | * Adventure.findById(id, function (err, adventure) {}); | |
| 954 | * | |
| 955 | * // same as above | |
| 956 | * Adventure.findById(id).exec(callback); | |
| 957 | * | |
| 958 | * // select only the adventures name and length | |
| 959 | * Adventure.findById(id, 'name length', function (err, adventure) {}); | |
| 960 | * | |
| 961 | * // same as above | |
| 962 | * Adventure.findById(id, 'name length').exec(callback); | |
| 963 | * | |
| 964 | * // include all properties except for `length` | |
| 965 | * Adventure.findById(id, '-length').exec(function (err, adventure) {}); | |
| 966 | * | |
| 967 | * // passing options (in this case return the raw js objects, not mongoose documents by passing `lean` | |
| 968 | * Adventure.findById(id, 'name', { lean: true }, function (err, doc) {}); | |
| 969 | * | |
| 970 | * // same as above | |
| 971 | * Adventure.findById(id, 'name').lean().exec(function (err, doc) {}); | |
| 972 | * | |
| 973 | * @param {ObjectId|HexId} id objectid, or a value that can be casted to one | |
| 974 | * @param {Object} [fields] optional fields to select | |
| 975 | * @param {Object} [options] optional | |
| 976 | * @param {Function} [callback] | |
| 977 | * @return {Query} | |
| 978 | * @see field selection #query_Query-select | |
| 979 | * @see lean queries #query_Query-lean | |
| 980 | * @api public | |
| 981 | */ | |
| 982 | ||
| 983 | 1 | Model.findById = function findById (id, fields, options, callback) { |
| 984 | 0 | return this.findOne({ _id: id }, fields, options, callback); |
| 985 | }; | |
| 986 | ||
| 987 | /** | |
| 988 | * Finds one document. | |
| 989 | * | |
| 990 | * The `conditions` are cast to their respective SchemaTypes before the command is sent. | |
| 991 | * | |
| 992 | * ####Example: | |
| 993 | * | |
| 994 | * // find one iphone adventures - iphone adventures?? | |
| 995 | * Adventure.findOne({ type: 'iphone' }, function (err, adventure) {}); | |
| 996 | * | |
| 997 | * // same as above | |
| 998 | * Adventure.findOne({ type: 'iphone' }).exec(function (err, adventure) {}); | |
| 999 | * | |
| 1000 | * // select only the adventures name | |
| 1001 | * Adventure.findOne({ type: 'iphone' }, 'name', function (err, adventure) {}); | |
| 1002 | * | |
| 1003 | * // same as above | |
| 1004 | * Adventure.findOne({ type: 'iphone' }, 'name').exec(function (err, adventure) {}); | |
| 1005 | * | |
| 1006 | * // specify options, in this case lean | |
| 1007 | * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }, callback); | |
| 1008 | * | |
| 1009 | * // same as above | |
| 1010 | * Adventure.findOne({ type: 'iphone' }, 'name', { lean: true }).exec(callback); | |
| 1011 | * | |
| 1012 | * // chaining findOne queries (same as above) | |
| 1013 | * Adventure.findOne({ type: 'iphone' }).select('name').lean().exec(callback); | |
| 1014 | * | |
| 1015 | * @param {Object} conditions | |
| 1016 | * @param {Object} [fields] optional fields to select | |
| 1017 | * @param {Object} [options] optional | |
| 1018 | * @param {Function} [callback] | |
| 1019 | * @return {Query} | |
| 1020 | * @see field selection #query_Query-select | |
| 1021 | * @see lean queries #query_Query-lean | |
| 1022 | * @api public | |
| 1023 | */ | |
| 1024 | ||
| 1025 | 1 | Model.findOne = function findOne (conditions, fields, options, callback) { |
| 1026 | 0 | if ('function' == typeof options) { |
| 1027 | 0 | callback = options; |
| 1028 | 0 | options = null; |
| 1029 | 0 | } else if ('function' == typeof fields) { |
| 1030 | 0 | callback = fields; |
| 1031 | 0 | fields = null; |
| 1032 | 0 | options = null; |
| 1033 | 0 | } else if ('function' == typeof conditions) { |
| 1034 | 0 | callback = conditions; |
| 1035 | 0 | conditions = {}; |
| 1036 | 0 | fields = null; |
| 1037 | 0 | options = null; |
| 1038 | } | |
| 1039 | ||
| 1040 | // get the mongodb collection object | |
| 1041 | 0 | var mq = new Query({}, options, this, this.collection); |
| 1042 | 0 | mq.select(fields); |
| 1043 | 0 | if (this.schema.discriminatorMapping && mq._selectedInclusively()) { |
| 1044 | 0 | mq.select(this.schema.options.discriminatorKey); |
| 1045 | } | |
| 1046 | ||
| 1047 | 0 | return mq.findOne(conditions, callback); |
| 1048 | }; | |
| 1049 | ||
| 1050 | /** | |
| 1051 | * Counts number of matching documents in a database collection. | |
| 1052 | * | |
| 1053 | * ####Example: | |
| 1054 | * | |
| 1055 | * Adventure.count({ type: 'jungle' }, function (err, count) { | |
| 1056 | * if (err) .. | |
| 1057 | * console.log('there are %d jungle adventures', count); | |
| 1058 | * }); | |
| 1059 | * | |
| 1060 | * @param {Object} conditions | |
| 1061 | * @param {Function} [callback] | |
| 1062 | * @return {Query} | |
| 1063 | * @api public | |
| 1064 | */ | |
| 1065 | ||
| 1066 | 1 | Model.count = function count (conditions, callback) { |
| 1067 | 0 | if ('function' === typeof conditions) |
| 1068 | 0 | callback = conditions, conditions = {}; |
| 1069 | ||
| 1070 | // get the mongodb collection object | |
| 1071 | 0 | var mq = new Query({}, {}, this, this.collection); |
| 1072 | ||
| 1073 | 0 | return mq.count(conditions, callback); |
| 1074 | }; | |
| 1075 | ||
| 1076 | /** | |
| 1077 | * Creates a Query for a `distinct` operation. | |
| 1078 | * | |
| 1079 | * Passing a `callback` immediately executes the query. | |
| 1080 | * | |
| 1081 | * ####Example | |
| 1082 | * | |
| 1083 | * Link.distinct('url', { clicks: {$gt: 100}}, function (err, result) { | |
| 1084 | * if (err) return handleError(err); | |
| 1085 | * | |
| 1086 | * assert(Array.isArray(result)); | |
| 1087 | * console.log('unique urls with more than 100 clicks', result); | |
| 1088 | * }) | |
| 1089 | * | |
| 1090 | * var query = Link.distinct('url'); | |
| 1091 | * query.exec(callback); | |
| 1092 | * | |
| 1093 | * @param {String} field | |
| 1094 | * @param {Object} [conditions] optional | |
| 1095 | * @param {Function} [callback] | |
| 1096 | * @return {Query} | |
| 1097 | * @api public | |
| 1098 | */ | |
| 1099 | ||
| 1100 | 1 | Model.distinct = function distinct (field, conditions, callback) { |
| 1101 | // get the mongodb collection object | |
| 1102 | 0 | var mq = new Query({}, {}, this, this.collection); |
| 1103 | ||
| 1104 | 0 | if ('function' == typeof conditions) { |
| 1105 | 0 | callback = conditions; |
| 1106 | 0 | conditions = {}; |
| 1107 | } | |
| 1108 | ||
| 1109 | 0 | return mq.distinct(conditions, field, callback); |
| 1110 | }; | |
| 1111 | ||
| 1112 | /** | |
| 1113 | * Creates a Query, applies the passed conditions, and returns the Query. | |
| 1114 | * | |
| 1115 | * For example, instead of writing: | |
| 1116 | * | |
| 1117 | * User.find({age: {$gte: 21, $lte: 65}}, callback); | |
| 1118 | * | |
| 1119 | * we can instead write: | |
| 1120 | * | |
| 1121 | * User.where('age').gte(21).lte(65).exec(callback); | |
| 1122 | * | |
| 1123 | * Since the Query class also supports `where` you can continue chaining | |
| 1124 | * | |
| 1125 | * User | |
| 1126 | * .where('age').gte(21).lte(65) | |
| 1127 | * .where('name', /^b/i) | |
| 1128 | * ... etc | |
| 1129 | * | |
| 1130 | * @param {String} path | |
| 1131 | * @param {Object} [val] optional value | |
| 1132 | * @return {Query} | |
| 1133 | * @api public | |
| 1134 | */ | |
| 1135 | ||
| 1136 | 1 | Model.where = function where (path, val) { |
| 1137 | // get the mongodb collection object | |
| 1138 | 0 | var mq = new Query({}, {}, this, this.collection).find({}); |
| 1139 | 0 | return mq.where.apply(mq, arguments); |
| 1140 | }; | |
| 1141 | ||
| 1142 | /** | |
| 1143 | * Creates a `Query` and specifies a `$where` condition. | |
| 1144 | * | |
| 1145 | * Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model. | |
| 1146 | * | |
| 1147 | * Blog.$where('this.comments.length > 5').exec(function (err, docs) {}); | |
| 1148 | * | |
| 1149 | * @param {String|Function} argument is a javascript string or anonymous function | |
| 1150 | * @method $where | |
| 1151 | * @memberOf Model | |
| 1152 | * @return {Query} | |
| 1153 | * @see Query.$where #query_Query-%24where | |
| 1154 | * @api public | |
| 1155 | */ | |
| 1156 | ||
| 1157 | 1 | Model.$where = function $where () { |
| 1158 | 0 | var mq = new Query({}, {}, this, this.collection).find({}); |
| 1159 | 0 | return mq.$where.apply(mq, arguments); |
| 1160 | }; | |
| 1161 | ||
| 1162 | /** | |
| 1163 | * Issues a mongodb findAndModify update command. | |
| 1164 | * | |
| 1165 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed else a Query object is returned. | |
| 1166 | * | |
| 1167 | * ####Options: | |
| 1168 | * | |
| 1169 | * - `new`: bool - true to return the modified document rather than the original. defaults to true | |
| 1170 | * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. | |
| 1171 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1172 | * - `select`: sets the document fields to return | |
| 1173 | * | |
| 1174 | * ####Examples: | |
| 1175 | * | |
| 1176 | * A.findOneAndUpdate(conditions, update, options, callback) // executes | |
| 1177 | * A.findOneAndUpdate(conditions, update, options) // returns Query | |
| 1178 | * A.findOneAndUpdate(conditions, update, callback) // executes | |
| 1179 | * A.findOneAndUpdate(conditions, update) // returns Query | |
| 1180 | * A.findOneAndUpdate() // returns Query | |
| 1181 | * | |
| 1182 | * ####Note: | |
| 1183 | * | |
| 1184 | * All top level update keys which are not `atomic` operation names are treated as set operations: | |
| 1185 | * | |
| 1186 | * ####Example: | |
| 1187 | * | |
| 1188 | * var query = { name: 'borne' }; | |
| 1189 | * Model.findOneAndUpdate(query, { name: 'jason borne' }, options, callback) | |
| 1190 | * | |
| 1191 | * // is sent as | |
| 1192 | * Model.findOneAndUpdate(query, { $set: { name: 'jason borne' }}, options, callback) | |
| 1193 | * | |
| 1194 | * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`. | |
| 1195 | * | |
| 1196 | * ####Note: | |
| 1197 | * | |
| 1198 | * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied: | |
| 1199 | * | |
| 1200 | * - defaults | |
| 1201 | * - setters | |
| 1202 | * - validators | |
| 1203 | * - middleware | |
| 1204 | * | |
| 1205 | * If you need those features, use the traditional approach of first retrieving the document. | |
| 1206 | * | |
| 1207 | * Model.findOne({ name: 'borne' }, function (err, doc) { | |
| 1208 | * if (err) .. | |
| 1209 | * doc.name = 'jason borne'; | |
| 1210 | * doc.save(callback); | |
| 1211 | * }) | |
| 1212 | * | |
| 1213 | * @param {Object} [conditions] | |
| 1214 | * @param {Object} [update] | |
| 1215 | * @param {Object} [options] | |
| 1216 | * @param {Function} [callback] | |
| 1217 | * @return {Query} | |
| 1218 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1219 | * @api public | |
| 1220 | */ | |
| 1221 | ||
| 1222 | 1 | Model.findOneAndUpdate = function (conditions, update, options, callback) { |
| 1223 | 0 | if ('function' == typeof options) { |
| 1224 | 0 | callback = options; |
| 1225 | 0 | options = null; |
| 1226 | } | |
| 1227 | 0 | else if (1 === arguments.length) { |
| 1228 | 0 | if ('function' == typeof conditions) { |
| 1229 | 0 | var msg = 'Model.findOneAndUpdate(): First argument must not be a function.\n\n' |
| 1230 | + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options, callback)\n' | |
| 1231 | + ' ' + this.modelName + '.findOneAndUpdate(conditions, update, options)\n' | |
| 1232 | + ' ' + this.modelName + '.findOneAndUpdate(conditions, update)\n' | |
| 1233 | + ' ' + this.modelName + '.findOneAndUpdate(update)\n' | |
| 1234 | + ' ' + this.modelName + '.findOneAndUpdate()\n'; | |
| 1235 | 0 | throw new TypeError(msg) |
| 1236 | } | |
| 1237 | 0 | update = conditions; |
| 1238 | 0 | conditions = undefined; |
| 1239 | } | |
| 1240 | ||
| 1241 | 0 | var fields; |
| 1242 | 0 | if (options && options.fields) { |
| 1243 | 0 | fields = options.fields; |
| 1244 | 0 | options.fields = undefined; |
| 1245 | } | |
| 1246 | ||
| 1247 | 0 | var mq = new Query({}, {}, this, this.collection); |
| 1248 | 0 | mq.select(fields); |
| 1249 | ||
| 1250 | 0 | return mq.findOneAndUpdate(conditions, update, options, callback); |
| 1251 | } | |
| 1252 | ||
| 1253 | /** | |
| 1254 | * Issues a mongodb findAndModify update command by a documents id. | |
| 1255 | * | |
| 1256 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed else a Query object is returned. | |
| 1257 | * | |
| 1258 | * ####Options: | |
| 1259 | * | |
| 1260 | * - `new`: bool - true to return the modified document rather than the original. defaults to true | |
| 1261 | * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. | |
| 1262 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1263 | * - `select`: sets the document fields to return | |
| 1264 | * | |
| 1265 | * ####Examples: | |
| 1266 | * | |
| 1267 | * A.findByIdAndUpdate(id, update, options, callback) // executes | |
| 1268 | * A.findByIdAndUpdate(id, update, options) // returns Query | |
| 1269 | * A.findByIdAndUpdate(id, update, callback) // executes | |
| 1270 | * A.findByIdAndUpdate(id, update) // returns Query | |
| 1271 | * A.findByIdAndUpdate() // returns Query | |
| 1272 | * | |
| 1273 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed else a Query object is returned. | |
| 1274 | * | |
| 1275 | * ####Options: | |
| 1276 | * | |
| 1277 | * - `new`: bool - true to return the modified document rather than the original. defaults to true | |
| 1278 | * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. | |
| 1279 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1280 | * | |
| 1281 | * ####Note: | |
| 1282 | * | |
| 1283 | * All top level update keys which are not `atomic` operation names are treated as set operations: | |
| 1284 | * | |
| 1285 | * ####Example: | |
| 1286 | * | |
| 1287 | * Model.findByIdAndUpdate(id, { name: 'jason borne' }, options, callback) | |
| 1288 | * | |
| 1289 | * // is sent as | |
| 1290 | * Model.findByIdAndUpdate(id, { $set: { name: 'jason borne' }}, options, callback) | |
| 1291 | * | |
| 1292 | * This helps prevent accidentally overwriting your document with `{ name: 'jason borne' }`. | |
| 1293 | * | |
| 1294 | * ####Note: | |
| 1295 | * | |
| 1296 | * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied: | |
| 1297 | * | |
| 1298 | * - defaults | |
| 1299 | * - setters | |
| 1300 | * - validators | |
| 1301 | * - middleware | |
| 1302 | * | |
| 1303 | * If you need those features, use the traditional approach of first retrieving the document. | |
| 1304 | * | |
| 1305 | * Model.findById(id, function (err, doc) { | |
| 1306 | * if (err) .. | |
| 1307 | * doc.name = 'jason borne'; | |
| 1308 | * doc.save(callback); | |
| 1309 | * }) | |
| 1310 | * | |
| 1311 | * @param {ObjectId|HexId} id an ObjectId or string that can be cast to one. | |
| 1312 | * @param {Object} [update] | |
| 1313 | * @param {Object} [options] | |
| 1314 | * @param {Function} [callback] | |
| 1315 | * @return {Query} | |
| 1316 | * @see Model.findOneAndUpdate #model_Model.findOneAndUpdate | |
| 1317 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1318 | * @api public | |
| 1319 | */ | |
| 1320 | ||
| 1321 | 1 | Model.findByIdAndUpdate = function (id, update, options, callback) { |
| 1322 | 0 | var args; |
| 1323 | ||
| 1324 | 0 | if (1 === arguments.length) { |
| 1325 | 0 | if ('function' == typeof id) { |
| 1326 | 0 | var msg = 'Model.findByIdAndUpdate(): First argument must not be a function.\n\n' |
| 1327 | + ' ' + this.modelName + '.findByIdAndUpdate(id, callback)\n' | |
| 1328 | + ' ' + this.modelName + '.findByIdAndUpdate(id)\n' | |
| 1329 | + ' ' + this.modelName + '.findByIdAndUpdate()\n'; | |
| 1330 | 0 | throw new TypeError(msg) |
| 1331 | } | |
| 1332 | 0 | return this.findOneAndUpdate({_id: id }, undefined); |
| 1333 | } | |
| 1334 | ||
| 1335 | 0 | args = utils.args(arguments, 1); |
| 1336 | ||
| 1337 | // if a model is passed in instead of an id | |
| 1338 | 0 | if (id && id._id) { |
| 1339 | 0 | id = id._id; |
| 1340 | } | |
| 1341 | 0 | if (id) { |
| 1342 | 0 | args.unshift({ _id: id }); |
| 1343 | } | |
| 1344 | 0 | return this.findOneAndUpdate.apply(this, args); |
| 1345 | } | |
| 1346 | ||
| 1347 | /** | |
| 1348 | * Issue a mongodb findAndModify remove command. | |
| 1349 | * | |
| 1350 | * Finds a matching document, removes it, passing the found document (if any) to the callback. | |
| 1351 | * | |
| 1352 | * Executes immediately if `callback` is passed else a Query object is returned. | |
| 1353 | * | |
| 1354 | * ####Options: | |
| 1355 | * | |
| 1356 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1357 | * - `select`: sets the document fields to return | |
| 1358 | * | |
| 1359 | * ####Examples: | |
| 1360 | * | |
| 1361 | * A.findOneAndRemove(conditions, options, callback) // executes | |
| 1362 | * A.findOneAndRemove(conditions, options) // return Query | |
| 1363 | * A.findOneAndRemove(conditions, callback) // executes | |
| 1364 | * A.findOneAndRemove(conditions) // returns Query | |
| 1365 | * A.findOneAndRemove() // returns Query | |
| 1366 | * | |
| 1367 | * Although values are cast to their appropriate types when using the findAndModify helpers, the following are *not* applied: | |
| 1368 | * | |
| 1369 | * - defaults | |
| 1370 | * - setters | |
| 1371 | * - validators | |
| 1372 | * - middleware | |
| 1373 | * | |
| 1374 | * If you need those features, use the traditional approach of first retrieving the document. | |
| 1375 | * | |
| 1376 | * Model.findById(id, function (err, doc) { | |
| 1377 | * if (err) .. | |
| 1378 | * doc.remove(callback); | |
| 1379 | * }) | |
| 1380 | * | |
| 1381 | * @param {Object} conditions | |
| 1382 | * @param {Object} [options] | |
| 1383 | * @param {Function} [callback] | |
| 1384 | * @return {Query} | |
| 1385 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1386 | * @api public | |
| 1387 | */ | |
| 1388 | ||
| 1389 | 1 | Model.findOneAndRemove = function (conditions, options, callback) { |
| 1390 | 0 | if (1 === arguments.length && 'function' == typeof conditions) { |
| 1391 | 0 | var msg = 'Model.findOneAndRemove(): First argument must not be a function.\n\n' |
| 1392 | + ' ' + this.modelName + '.findOneAndRemove(conditions, callback)\n' | |
| 1393 | + ' ' + this.modelName + '.findOneAndRemove(conditions)\n' | |
| 1394 | + ' ' + this.modelName + '.findOneAndRemove()\n'; | |
| 1395 | 0 | throw new TypeError(msg) |
| 1396 | } | |
| 1397 | ||
| 1398 | 0 | if ('function' == typeof options) { |
| 1399 | 0 | callback = options; |
| 1400 | 0 | options = undefined; |
| 1401 | } | |
| 1402 | ||
| 1403 | 0 | var fields; |
| 1404 | 0 | if (options) { |
| 1405 | 0 | fields = options.select; |
| 1406 | 0 | options.select = undefined; |
| 1407 | } | |
| 1408 | ||
| 1409 | 0 | var mq = new Query({}, {}, this, this.collection); |
| 1410 | 0 | mq.select(fields); |
| 1411 | ||
| 1412 | 0 | return mq.findOneAndRemove(conditions, options, callback); |
| 1413 | } | |
| 1414 | ||
| 1415 | /** | |
| 1416 | * Issue a mongodb findAndModify remove command by a documents id. | |
| 1417 | * | |
| 1418 | * Finds a matching document, removes it, passing the found document (if any) to the callback. | |
| 1419 | * | |
| 1420 | * Executes immediately if `callback` is passed, else a `Query` object is returned. | |
| 1421 | * | |
| 1422 | * ####Options: | |
| 1423 | * | |
| 1424 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1425 | * - `select`: sets the document fields to return | |
| 1426 | * | |
| 1427 | * ####Examples: | |
| 1428 | * | |
| 1429 | * A.findByIdAndRemove(id, options, callback) // executes | |
| 1430 | * A.findByIdAndRemove(id, options) // return Query | |
| 1431 | * A.findByIdAndRemove(id, callback) // executes | |
| 1432 | * A.findByIdAndRemove(id) // returns Query | |
| 1433 | * A.findByIdAndRemove() // returns Query | |
| 1434 | * | |
| 1435 | * @param {ObjectId|HexString} id ObjectId or string that can be cast to one | |
| 1436 | * @param {Object} [options] | |
| 1437 | * @param {Function} [callback] | |
| 1438 | * @return {Query} | |
| 1439 | * @see Model.findOneAndRemove #model_Model.findOneAndRemove | |
| 1440 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1441 | */ | |
| 1442 | ||
| 1443 | 1 | Model.findByIdAndRemove = function (id, options, callback) { |
| 1444 | 0 | if (1 === arguments.length && 'function' == typeof id) { |
| 1445 | 0 | var msg = 'Model.findByIdAndRemove(): First argument must not be a function.\n\n' |
| 1446 | + ' ' + this.modelName + '.findByIdAndRemove(id, callback)\n' | |
| 1447 | + ' ' + this.modelName + '.findByIdAndRemove(id)\n' | |
| 1448 | + ' ' + this.modelName + '.findByIdAndRemove()\n'; | |
| 1449 | 0 | throw new TypeError(msg) |
| 1450 | } | |
| 1451 | ||
| 1452 | 0 | return this.findOneAndRemove({ _id: id }, options, callback); |
| 1453 | } | |
| 1454 | ||
| 1455 | /** | |
| 1456 | * Shortcut for creating a new Document that is automatically saved to the db if valid. | |
| 1457 | * | |
| 1458 | * ####Example: | |
| 1459 | * | |
| 1460 | * // pass individual docs | |
| 1461 | * Candy.create({ type: 'jelly bean' }, { type: 'snickers' }, function (err, jellybean, snickers) { | |
| 1462 | * if (err) // ... | |
| 1463 | * }); | |
| 1464 | * | |
| 1465 | * // pass an array | |
| 1466 | * var array = [{ type: 'jelly bean' }, { type: 'snickers' }]; | |
| 1467 | * Candy.create(array, function (err, jellybean, snickers) { | |
| 1468 | * if (err) // ... | |
| 1469 | * }); | |
| 1470 | * | |
| 1471 | * // callback is optional; use the returned promise if you like: | |
| 1472 | * var promise = Candy.create({ type: 'jawbreaker' }); | |
| 1473 | * promise.then(function (jawbreaker) { | |
| 1474 | * // ... | |
| 1475 | * }) | |
| 1476 | * | |
| 1477 | * @param {Array|Object...} doc(s) | |
| 1478 | * @param {Function} [fn] callback | |
| 1479 | * @return {Promise} | |
| 1480 | * @api public | |
| 1481 | */ | |
| 1482 | ||
| 1483 | 1 | Model.create = function create (doc, fn) { |
| 1484 | 0 | var promise = new Promise |
| 1485 | , args | |
| 1486 | ||
| 1487 | 0 | if (Array.isArray(doc)) { |
| 1488 | 0 | args = doc; |
| 1489 | ||
| 1490 | 0 | if ('function' == typeof fn) { |
| 1491 | 0 | promise.onResolve(fn); |
| 1492 | } | |
| 1493 | ||
| 1494 | } else { | |
| 1495 | 0 | var last = arguments[arguments.length - 1]; |
| 1496 | ||
| 1497 | 0 | if ('function' == typeof last) { |
| 1498 | 0 | promise.onResolve(last); |
| 1499 | 0 | args = utils.args(arguments, 0, arguments.length - 1); |
| 1500 | } else { | |
| 1501 | 0 | args = utils.args(arguments); |
| 1502 | } | |
| 1503 | } | |
| 1504 | ||
| 1505 | 0 | var count = args.length; |
| 1506 | ||
| 1507 | 0 | if (0 === count) { |
| 1508 | 0 | promise.complete(); |
| 1509 | 0 | return promise; |
| 1510 | } | |
| 1511 | ||
| 1512 | 0 | var self = this; |
| 1513 | 0 | var docs = []; |
| 1514 | ||
| 1515 | 0 | args.forEach(function (arg, i) { |
| 1516 | 0 | var doc = new self(arg); |
| 1517 | 0 | docs[i] = doc; |
| 1518 | 0 | doc.save(function (err) { |
| 1519 | 0 | if (err) return promise.error(err); |
| 1520 | 0 | --count || promise.complete.apply(promise, docs); |
| 1521 | }); | |
| 1522 | }); | |
| 1523 | ||
| 1524 | 0 | return promise; |
| 1525 | }; | |
| 1526 | ||
| 1527 | /** | |
| 1528 | * Updates documents in the database without returning them. | |
| 1529 | * | |
| 1530 | * ####Examples: | |
| 1531 | * | |
| 1532 | * MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn); | |
| 1533 | * MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, function (err, numberAffected, raw) { | |
| 1534 | * if (err) return handleError(err); | |
| 1535 | * console.log('The number of updated documents was %d', numberAffected); | |
| 1536 | * console.log('The raw response from Mongo was ', raw); | |
| 1537 | * }); | |
| 1538 | * | |
| 1539 | * ####Valid options: | |
| 1540 | * | |
| 1541 | * - `safe` (boolean) safe mode (defaults to value set in schema (true)) | |
| 1542 | * - `upsert` (boolean) whether to create the doc if it doesn't match (false) | |
| 1543 | * - `multi` (boolean) whether multiple documents should be updated (false) | |
| 1544 | * - `strict` (boolean) overrides the `strict` option for this update | |
| 1545 | * | |
| 1546 | * All `update` values are cast to their appropriate SchemaTypes before being sent. | |
| 1547 | * | |
| 1548 | * The `callback` function receives `(err, numberAffected, rawResponse)`. | |
| 1549 | * | |
| 1550 | * - `err` is the error if any occurred | |
| 1551 | * - `numberAffected` is the count of updated documents Mongo reported | |
| 1552 | * - `rawResponse` is the full response from Mongo | |
| 1553 | * | |
| 1554 | * ####Note: | |
| 1555 | * | |
| 1556 | * All top level keys which are not `atomic` operation names are treated as set operations: | |
| 1557 | * | |
| 1558 | * ####Example: | |
| 1559 | * | |
| 1560 | * var query = { name: 'borne' }; | |
| 1561 | * Model.update(query, { name: 'jason borne' }, options, callback) | |
| 1562 | * | |
| 1563 | * // is sent as | |
| 1564 | * Model.update(query, { $set: { name: 'jason borne' }}, options, callback) | |
| 1565 | * | |
| 1566 | * This helps prevent accidentally overwriting all documents in your collection with `{ name: 'jason borne' }`. | |
| 1567 | * | |
| 1568 | * ####Note: | |
| 1569 | * | |
| 1570 | * Be careful to not use an existing model instance for the update clause (this won't work and can cause weird behavior like infinite loops). Also, ensure that the update clause does not have an _id property, which causes Mongo to return a "Mod on _id not allowed" error. | |
| 1571 | * | |
| 1572 | * ####Note: | |
| 1573 | * | |
| 1574 | * To update documents without waiting for a response from MongoDB, do not pass a `callback`, then call `exec` on the returned [Query](#query-js): | |
| 1575 | * | |
| 1576 | * Comment.update({ _id: id }, { $set: { text: 'changed' }}).exec(); | |
| 1577 | * | |
| 1578 | * ####Note: | |
| 1579 | * | |
| 1580 | * Although values are casted to their appropriate types when using update, the following are *not* applied: | |
| 1581 | * | |
| 1582 | * - defaults | |
| 1583 | * - setters | |
| 1584 | * - validators | |
| 1585 | * - middleware | |
| 1586 | * | |
| 1587 | * If you need those features, use the traditional approach of first retrieving the document. | |
| 1588 | * | |
| 1589 | * Model.findOne({ name: 'borne' }, function (err, doc) { | |
| 1590 | * if (err) .. | |
| 1591 | * doc.name = 'jason borne'; | |
| 1592 | * doc.save(callback); | |
| 1593 | * }) | |
| 1594 | * | |
| 1595 | * @see strict http://mongoosejs.com/docs/guide.html#strict | |
| 1596 | * @param {Object} conditions | |
| 1597 | * @param {Object} update | |
| 1598 | * @param {Object} [options] | |
| 1599 | * @param {Function} [callback] | |
| 1600 | * @return {Query} | |
| 1601 | * @api public | |
| 1602 | */ | |
| 1603 | ||
| 1604 | 1 | Model.update = function update (conditions, doc, options, callback) { |
| 1605 | 0 | var mq = new Query({}, {}, this, this.collection); |
| 1606 | 0 | return mq.update(conditions, doc, options, callback); |
| 1607 | }; | |
| 1608 | ||
| 1609 | /** | |
| 1610 | * Executes a mapReduce command. | |
| 1611 | * | |
| 1612 | * `o` is an object specifying all mapReduce options as well as the map and reduce functions. All options are delegated to the driver implementation. | |
| 1613 | * | |
| 1614 | * ####Example: | |
| 1615 | * | |
| 1616 | * var o = {}; | |
| 1617 | * o.map = function () { emit(this.name, 1) } | |
| 1618 | * o.reduce = function (k, vals) { return vals.length } | |
| 1619 | * User.mapReduce(o, function (err, results) { | |
| 1620 | * console.log(results) | |
| 1621 | * }) | |
| 1622 | * | |
| 1623 | * ####Other options: | |
| 1624 | * | |
| 1625 | * - `query` {Object} query filter object. | |
| 1626 | * - `limit` {Number} max number of documents | |
| 1627 | * - `keeptemp` {Boolean, default:false} keep temporary data | |
| 1628 | * - `finalize` {Function} finalize function | |
| 1629 | * - `scope` {Object} scope variables exposed to map/reduce/finalize during execution | |
| 1630 | * - `jsMode` {Boolean, default:false} it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X | |
| 1631 | * - `verbose` {Boolean, default:false} provide statistics on job execution time. | |
| 1632 | * - `out*` {Object, default: {inline:1}} sets the output target for the map reduce job. | |
| 1633 | * | |
| 1634 | * ####* out options: | |
| 1635 | * | |
| 1636 | * - `{inline:1}` the results are returned in an array | |
| 1637 | * - `{replace: 'collectionName'}` add the results to collectionName: the results replace the collection | |
| 1638 | * - `{reduce: 'collectionName'}` add the results to collectionName: if dups are detected, uses the reducer / finalize functions | |
| 1639 | * - `{merge: 'collectionName'}` add the results to collectionName: if dups exist the new docs overwrite the old | |
| 1640 | * | |
| 1641 | * If `options.out` is set to `replace`, `merge`, or `reduce`, a Model instance is returned that can be used for further querying. Queries run against this model are all executed with the `lean` option; meaning only the js object is returned and no Mongoose magic is applied (getters, setters, etc). | |
| 1642 | * | |
| 1643 | * ####Example: | |
| 1644 | * | |
| 1645 | * var o = {}; | |
| 1646 | * o.map = function () { emit(this.name, 1) } | |
| 1647 | * o.reduce = function (k, vals) { return vals.length } | |
| 1648 | * o.out = { replace: 'createdCollectionNameForResults' } | |
| 1649 | * o.verbose = true; | |
| 1650 | * | |
| 1651 | * User.mapReduce(o, function (err, model, stats) { | |
| 1652 | * console.log('map reduce took %d ms', stats.processtime) | |
| 1653 | * model.find().where('value').gt(10).exec(function (err, docs) { | |
| 1654 | * console.log(docs); | |
| 1655 | * }); | |
| 1656 | * }) | |
| 1657 | * | |
| 1658 | * // a promise is returned so you may instead write | |
| 1659 | * var promise = User.mapReduce(o); | |
| 1660 | * promise.then(function (model, stats) { | |
| 1661 | * console.log('map reduce took %d ms', stats.processtime) | |
| 1662 | * return model.find().where('value').gt(10).exec(); | |
| 1663 | * }).then(function (docs) { | |
| 1664 | * console.log(docs); | |
| 1665 | * }).then(null, handleError).end() | |
| 1666 | * | |
| 1667 | * @param {Object} o an object specifying map-reduce options | |
| 1668 | * @param {Function} [callback] optional callback | |
| 1669 | * @see http://www.mongodb.org/display/DOCS/MapReduce | |
| 1670 | * @return {Promise} | |
| 1671 | * @api public | |
| 1672 | */ | |
| 1673 | ||
| 1674 | 1 | Model.mapReduce = function mapReduce (o, callback) { |
| 1675 | 0 | var promise = new Promise(callback); |
| 1676 | 0 | var self = this; |
| 1677 | ||
| 1678 | 0 | if (!Model.mapReduce.schema) { |
| 1679 | 0 | var opts = { noId: true, noVirtualId: true, strict: false } |
| 1680 | 0 | Model.mapReduce.schema = new Schema({}, opts); |
| 1681 | } | |
| 1682 | ||
| 1683 | 0 | if (!o.out) o.out = { inline: 1 }; |
| 1684 | 0 | if (false !== o.verbose) o.verbose = true; |
| 1685 | ||
| 1686 | 0 | o.map = String(o.map); |
| 1687 | 0 | o.reduce = String(o.reduce); |
| 1688 | ||
| 1689 | 0 | if (o.query) { |
| 1690 | 0 | var q = new Query(o.query); |
| 1691 | 0 | q.cast(this); |
| 1692 | 0 | o.query = q._conditions; |
| 1693 | 0 | q = undefined; |
| 1694 | } | |
| 1695 | ||
| 1696 | 0 | this.collection.mapReduce(null, null, o, function (err, ret, stats) { |
| 1697 | 0 | if (err) return promise.error(err); |
| 1698 | ||
| 1699 | 0 | if (ret.findOne && ret.mapReduce) { |
| 1700 | // returned a collection, convert to Model | |
| 1701 | 0 | var model = Model.compile( |
| 1702 | '_mapreduce_' + ret.collectionName | |
| 1703 | , Model.mapReduce.schema | |
| 1704 | , ret.collectionName | |
| 1705 | , self.db | |
| 1706 | , self.base); | |
| 1707 | ||
| 1708 | 0 | model._mapreduce = true; |
| 1709 | ||
| 1710 | 0 | return promise.fulfill(model, stats); |
| 1711 | } | |
| 1712 | ||
| 1713 | 0 | promise.fulfill(ret, stats); |
| 1714 | }); | |
| 1715 | ||
| 1716 | 0 | return promise; |
| 1717 | } | |
| 1718 | ||
| 1719 | /** | |
| 1720 | * geoNear support for Mongoose | |
| 1721 | * | |
| 1722 | * ####Options: | |
| 1723 | * - `lean` {Boolean} return the raw object | |
| 1724 | * - All options supported by the driver are also supported | |
| 1725 | * | |
| 1726 | * ####Example: | |
| 1727 | * | |
| 1728 | * // Legacy point | |
| 1729 | * Model.geoNear([1,3], { maxDistance : 5, spherical : true }, function(err, results, stats) { | |
| 1730 | * console.log(results); | |
| 1731 | * }); | |
| 1732 | * | |
| 1733 | * // geoJson | |
| 1734 | * var point = { type : "Point", coordinates : [9,9] }; | |
| 1735 | * Model.geoNear(point, { maxDistance : 5, spherical : true }, function(err, results, stats) { | |
| 1736 | * console.log(results); | |
| 1737 | * }); | |
| 1738 | * | |
| 1739 | * @param {Object/Array} GeoJSON point or legacy coordinate pair [x,y] to search near | |
| 1740 | * @param {Object} options for the qurery | |
| 1741 | * @param {Function} [callback] optional callback for the query | |
| 1742 | * @return {Promise} | |
| 1743 | * @see http://docs.mongodb.org/manual/core/2dsphere/ | |
| 1744 | * @see http://mongodb.github.io/node-mongodb-native/api-generated/collection.html?highlight=geonear#geoNear | |
| 1745 | * @api public | |
| 1746 | */ | |
| 1747 | ||
| 1748 | 1 | Model.geoNear = function (near, options, callback) { |
| 1749 | 0 | if ('function' == typeof options) { |
| 1750 | 0 | callback = options; |
| 1751 | 0 | options = {}; |
| 1752 | } | |
| 1753 | ||
| 1754 | 0 | var promise = new Promise(callback); |
| 1755 | 0 | if (!near) { |
| 1756 | 0 | promise.error(new Error("Must pass a near option to geoNear")); |
| 1757 | 0 | return promise; |
| 1758 | } | |
| 1759 | ||
| 1760 | 0 | var x,y; |
| 1761 | ||
| 1762 | 0 | if (Array.isArray(near)) { |
| 1763 | 0 | if (near.length != 2) { |
| 1764 | 0 | promise.error(new Error("If using legacy coordinates, must be an array of size 2 for geoNear")); |
| 1765 | 0 | return promise; |
| 1766 | } | |
| 1767 | 0 | x = near[0]; |
| 1768 | 0 | y = near[1]; |
| 1769 | } else { | |
| 1770 | 0 | if (near.type != "Point" || !Array.isArray(near.coordinates)) { |
| 1771 | 0 | promise.error(new Error("Must pass either a legacy coordinate array or GeoJSON Point to geoNear")); |
| 1772 | 0 | return promise; |
| 1773 | } | |
| 1774 | ||
| 1775 | 0 | x = near.coordinates[0]; |
| 1776 | 0 | y = near.coordinates[1]; |
| 1777 | } | |
| 1778 | ||
| 1779 | 0 | var self = this; |
| 1780 | 0 | this.collection.geoNear(x, y, options, function (err, res) { |
| 1781 | 0 | if (err) return promise.error(err); |
| 1782 | 0 | if (options.lean) return promise.fulfill(res.results, res.stats); |
| 1783 | ||
| 1784 | 0 | var count = res.results.length; |
| 1785 | // if there are no results, fulfill the promise now | |
| 1786 | 0 | if (count == 0) { |
| 1787 | 0 | return promise.fulfill(res.results, res.stats); |
| 1788 | } | |
| 1789 | ||
| 1790 | 0 | var errSeen = false; |
| 1791 | 0 | for (var i=0; i < res.results.length; i++) { |
| 1792 | 0 | var temp = res.results[i].obj; |
| 1793 | 0 | res.results[i].obj = new self(); |
| 1794 | 0 | res.results[i].obj.init(temp, function (err) { |
| 1795 | 0 | if (err && !errSeen) { |
| 1796 | 0 | errSeen = true; |
| 1797 | 0 | return promise.error(err); |
| 1798 | } | |
| 1799 | 0 | --count || promise.fulfill(res.results, res.stats); |
| 1800 | }); | |
| 1801 | } | |
| 1802 | }); | |
| 1803 | 0 | return promise; |
| 1804 | }; | |
| 1805 | ||
| 1806 | /** | |
| 1807 | * Performs [aggregations](http://docs.mongodb.org/manual/applications/aggregation/) on the models collection. | |
| 1808 | * | |
| 1809 | * If a `callback` is passed, the `aggregate` is executed and a `Promise` is returned. If a callback is not passed, the `aggregate` itself is returned. | |
| 1810 | * | |
| 1811 | * ####Example: | |
| 1812 | * | |
| 1813 | * // Find the max balance of all accounts | |
| 1814 | * Users.aggregate( | |
| 1815 | * { $group: { _id: null, maxBalance: { $max: '$balance' }}} | |
| 1816 | * , { $project: { _id: 0, maxBalance: 1 }} | |
| 1817 | * , function (err, res) { | |
| 1818 | * if (err) return handleError(err); | |
| 1819 | * console.log(res); // [ { maxBalance: 98000 } ] | |
| 1820 | * }); | |
| 1821 | * | |
| 1822 | * // Or use the aggregation pipeline builder. | |
| 1823 | * Users.aggregate() | |
| 1824 | * .group({ _id: null, maxBalance: { $max: '$balance' } }) | |
| 1825 | * .select('-id maxBalance') | |
| 1826 | * .exec(function (err, res) { | |
| 1827 | * if (err) return handleError(err); | |
| 1828 | * console.log(res); // [ { maxBalance: 98 } ] | |
| 1829 | * }); | |
| 1830 | * | |
| 1831 | * ####NOTE: | |
| 1832 | * | |
| 1833 | * - Arguments are not cast to the model's schema because `$project` operators allow redefining the "shape" of the documents at any stage of the pipeline, which may leave documents in an incompatible format. | |
| 1834 | * - The documents returned are plain javascript objects, not mongoose documents (since any shape of document can be returned). | |
| 1835 | * - Requires MongoDB >= 2.1 | |
| 1836 | * | |
| 1837 | * @see Aggregate #aggregate_Aggregate | |
| 1838 | * @see MongoDB http://docs.mongodb.org/manual/applications/aggregation/ | |
| 1839 | * @param {Object|Array} [...] aggregation pipeline operator(s) or operator array | |
| 1840 | * @param {Function} [callback] | |
| 1841 | * @return {Aggregate|Promise} | |
| 1842 | * @api public | |
| 1843 | */ | |
| 1844 | ||
| 1845 | 1 | Model.aggregate = function aggregate () { |
| 1846 | 0 | var args = [].slice.call(arguments) |
| 1847 | , aggregate | |
| 1848 | , callback; | |
| 1849 | ||
| 1850 | 0 | if ('function' === typeof args[args.length - 1]) { |
| 1851 | 0 | callback = args.pop(); |
| 1852 | } | |
| 1853 | ||
| 1854 | 0 | if (1 === args.length && util.isArray(args[0])) { |
| 1855 | 0 | aggregate = new Aggregate(args[0]); |
| 1856 | } else { | |
| 1857 | 0 | aggregate = new Aggregate(args); |
| 1858 | } | |
| 1859 | ||
| 1860 | 0 | aggregate.bind(this); |
| 1861 | ||
| 1862 | 0 | if ('undefined' === typeof callback) { |
| 1863 | 0 | return aggregate; |
| 1864 | } | |
| 1865 | ||
| 1866 | 0 | return aggregate.exec(callback); |
| 1867 | } | |
| 1868 | ||
| 1869 | /** | |
| 1870 | * Implements `$geoSearch` functionality for Mongoose | |
| 1871 | * | |
| 1872 | * ####Example: | |
| 1873 | * | |
| 1874 | * var options = { near: [10, 10], maxDistance: 5 }; | |
| 1875 | * Locations.geoSearch({ type : "house" }, options, function(err, res) { | |
| 1876 | * console.log(res); | |
| 1877 | * }); | |
| 1878 | * | |
| 1879 | * ####Options: | |
| 1880 | * - `near` {Array} x,y point to search for | |
| 1881 | * - `maxDistance` {Number} the maximum distance from the point near that a result can be | |
| 1882 | * - `limit` {Number} The maximum number of results to return | |
| 1883 | * - `lean` {Boolean} return the raw object instead of the Mongoose Model | |
| 1884 | * | |
| 1885 | * @param {Object} condition an object that specifies the match condition (required) | |
| 1886 | * @param {Object} options for the geoSearch, some (near, maxDistance) are required | |
| 1887 | * @param {Function} [callback] optional callback | |
| 1888 | * @return {Promise} | |
| 1889 | * @see http://docs.mongodb.org/manual/reference/command/geoSearch/ | |
| 1890 | * @see http://docs.mongodb.org/manual/core/geohaystack/ | |
| 1891 | * @api public | |
| 1892 | */ | |
| 1893 | ||
| 1894 | 1 | Model.geoSearch = function (conditions, options, callback) { |
| 1895 | 0 | if ('function' == typeof options) { |
| 1896 | 0 | callback = options; |
| 1897 | 0 | options = {}; |
| 1898 | } | |
| 1899 | ||
| 1900 | 0 | var promise = new Promise(callback); |
| 1901 | ||
| 1902 | 0 | if (conditions == undefined || !utils.isObject(conditions)) { |
| 1903 | 0 | return promise.error(new Error("Must pass conditions to geoSearch")); |
| 1904 | } | |
| 1905 | ||
| 1906 | 0 | if (!options.near) { |
| 1907 | 0 | return promise.error(new Error("Must specify the near option in geoSearch")); |
| 1908 | } | |
| 1909 | ||
| 1910 | 0 | if (!Array.isArray(options.near)) { |
| 1911 | 0 | return promise.error(new Error("near option must be an array [x, y]")); |
| 1912 | } | |
| 1913 | ||
| 1914 | ||
| 1915 | // send the conditions in the options object | |
| 1916 | 0 | options.search = conditions; |
| 1917 | 0 | var self = this; |
| 1918 | ||
| 1919 | 0 | this.collection.geoHaystackSearch(options.near[0], options.near[1], options, function (err, res) { |
| 1920 | // have to deal with driver problem. Should be fixed in a soon-ish release | |
| 1921 | // (7/8/2013) | |
| 1922 | 0 | if (err || res.errmsg) { |
| 1923 | 0 | if (!err) err = new Error(res.errmsg); |
| 1924 | 0 | if (res && res.code !== undefined) err.code = res.code; |
| 1925 | 0 | return promise.error(err); |
| 1926 | } | |
| 1927 | ||
| 1928 | 0 | var count = res.results.length; |
| 1929 | 0 | if (options.lean || (count == 0)) return promise.fulfill(res.results, res.stats); |
| 1930 | ||
| 1931 | 0 | var errSeen = false; |
| 1932 | 0 | for (var i=0; i < res.results.length; i++) { |
| 1933 | 0 | var temp = res.results[i]; |
| 1934 | 0 | res.results[i] = new self(); |
| 1935 | 0 | res.results[i].init(temp, {}, function (err) { |
| 1936 | 0 | if (err && !errSeen) { |
| 1937 | 0 | errSeen = true; |
| 1938 | 0 | return promise.error(err); |
| 1939 | } | |
| 1940 | ||
| 1941 | 0 | --count || (!errSeen && promise.fulfill(res.results, res.stats)); |
| 1942 | }); | |
| 1943 | } | |
| 1944 | }); | |
| 1945 | ||
| 1946 | 0 | return promise; |
| 1947 | }; | |
| 1948 | ||
| 1949 | /** | |
| 1950 | * Populates document references. | |
| 1951 | * | |
| 1952 | * ####Available options: | |
| 1953 | * | |
| 1954 | * - path: space delimited path(s) to populate | |
| 1955 | * - select: optional fields to select | |
| 1956 | * - match: optional query conditions to match | |
| 1957 | * - model: optional name of the model to use for population | |
| 1958 | * - options: optional query options like sort, limit, etc | |
| 1959 | * | |
| 1960 | * ####Examples: | |
| 1961 | * | |
| 1962 | * // populates a single object | |
| 1963 | * User.findById(id, function (err, user) { | |
| 1964 | * var opts = [ | |
| 1965 | * { path: 'company', match: { x: 1 }, select: 'name' } | |
| 1966 | * , { path: 'notes', options: { limit: 10 }, model: 'override' } | |
| 1967 | * ] | |
| 1968 | * | |
| 1969 | * User.populate(user, opts, function (err, user) { | |
| 1970 | * console.log(user); | |
| 1971 | * }) | |
| 1972 | * }) | |
| 1973 | * | |
| 1974 | * // populates an array of objects | |
| 1975 | * User.find(match, function (err, users) { | |
| 1976 | * var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }] | |
| 1977 | * | |
| 1978 | * var promise = User.populate(users, opts); | |
| 1979 | * promise.then(console.log).end(); | |
| 1980 | * }) | |
| 1981 | * | |
| 1982 | * // imagine a Weapon model exists with two saved documents: | |
| 1983 | * // { _id: 389, name: 'whip' } | |
| 1984 | * // { _id: 8921, name: 'boomerang' } | |
| 1985 | * | |
| 1986 | * var user = { name: 'Indiana Jones', weapon: 389 } | |
| 1987 | * Weapon.populate(user, { path: 'weapon', model: 'Weapon' }, function (err, user) { | |
| 1988 | * console.log(user.weapon.name) // whip | |
| 1989 | * }) | |
| 1990 | * | |
| 1991 | * // populate many plain objects | |
| 1992 | * var users = [{ name: 'Indiana Jones', weapon: 389 }] | |
| 1993 | * users.push({ name: 'Batman', weapon: 8921 }) | |
| 1994 | * Weapon.populate(users, { path: 'weapon' }, function (err, users) { | |
| 1995 | * users.forEach(function (user) { | |
| 1996 | * console.log('%s uses a %s', users.name, user.weapon.name) | |
| 1997 | * // Indiana Jones uses a whip | |
| 1998 | * // Batman uses a boomerang | |
| 1999 | * }) | |
| 2000 | * }) | |
| 2001 | * // Note that we didn't need to specify the Weapon model because | |
| 2002 | * // we were already using it's populate() method. | |
| 2003 | * | |
| 2004 | * @param {Document|Array} docs Either a single document or array of documents to populate. | |
| 2005 | * @param {Object} options A hash of key/val (path, options) used for population. | |
| 2006 | * @param {Function} [cb(err,doc)] Optional callback, executed upon completion. Receives `err` and the `doc(s)`. | |
| 2007 | * @return {Promise} | |
| 2008 | * @api public | |
| 2009 | */ | |
| 2010 | ||
| 2011 | 1 | Model.populate = function (docs, paths, cb) { |
| 2012 | 0 | var promise = new Promise(cb); |
| 2013 | ||
| 2014 | // always resolve on nextTick for consistent async behavior | |
| 2015 | 0 | function resolve () { |
| 2016 | 0 | var args = utils.args(arguments); |
| 2017 | 0 | process.nextTick(function () { |
| 2018 | 0 | promise.resolve.apply(promise, args); |
| 2019 | }); | |
| 2020 | } | |
| 2021 | ||
| 2022 | // normalized paths | |
| 2023 | 0 | var paths = utils.populate(paths); |
| 2024 | 0 | var pending = paths.length; |
| 2025 | ||
| 2026 | 0 | if (0 === pending) { |
| 2027 | 0 | resolve(null, docs); |
| 2028 | 0 | return promise; |
| 2029 | } | |
| 2030 | ||
| 2031 | // each path has its own query options and must be executed separately | |
| 2032 | 0 | var i = pending; |
| 2033 | 0 | var path; |
| 2034 | 0 | while (i--) { |
| 2035 | 0 | path = paths[i]; |
| 2036 | 0 | populate(this, docs, path, next); |
| 2037 | } | |
| 2038 | ||
| 2039 | 0 | return promise; |
| 2040 | ||
| 2041 | 0 | function next (err) { |
| 2042 | 0 | if (err) return resolve(err); |
| 2043 | 0 | if (--pending) return; |
| 2044 | 0 | resolve(null, docs); |
| 2045 | } | |
| 2046 | } | |
| 2047 | ||
| 2048 | /*! | |
| 2049 | * Populates `docs` | |
| 2050 | */ | |
| 2051 | ||
| 2052 | 1 | function populate (model, docs, options, cb) { |
| 2053 | 0 | var select = options.select |
| 2054 | , match = options.match | |
| 2055 | , path = options.path | |
| 2056 | ||
| 2057 | 0 | var schema = model._getSchema(path); |
| 2058 | 0 | var subpath; |
| 2059 | ||
| 2060 | // handle document arrays | |
| 2061 | 0 | if (schema && schema.caster) { |
| 2062 | 0 | schema = schema.caster; |
| 2063 | } | |
| 2064 | ||
| 2065 | // model name for the populate query | |
| 2066 | 0 | var modelName = options.model && options.model.modelName |
| 2067 | || options.model // query options | |
| 2068 | || schema && schema.options.ref // declared in schema | |
| 2069 | || model.modelName // an ad-hoc structure | |
| 2070 | ||
| 2071 | 0 | var Model = model.db.model(modelName); |
| 2072 | ||
| 2073 | // expose the model used | |
| 2074 | 0 | options.model = Model; |
| 2075 | ||
| 2076 | // normalize single / multiple docs passed | |
| 2077 | 0 | if (!Array.isArray(docs)) { |
| 2078 | 0 | docs = [docs]; |
| 2079 | } | |
| 2080 | ||
| 2081 | 0 | if (0 === docs.length || docs.every(utils.isNullOrUndefined)) { |
| 2082 | 0 | return cb(); |
| 2083 | } | |
| 2084 | ||
| 2085 | 0 | var rawIds = []; |
| 2086 | 0 | var i, doc, id; |
| 2087 | 0 | var len = docs.length; |
| 2088 | 0 | var ret; |
| 2089 | 0 | var found = 0; |
| 2090 | 0 | var isDocument; |
| 2091 | ||
| 2092 | 0 | for (i = 0; i < len; i++) { |
| 2093 | 0 | ret = undefined; |
| 2094 | 0 | doc = docs[i]; |
| 2095 | 0 | id = String(utils.getValue("_id", doc)); |
| 2096 | 0 | isDocument = !! doc.$__; |
| 2097 | ||
| 2098 | 0 | if (isDocument && !doc.isModified(path)) { |
| 2099 | // it is possible a previously populated path is being | |
| 2100 | // populated again. Because users can specify matcher | |
| 2101 | // clauses in their populate arguments we use the cached | |
| 2102 | // _ids from the original populate call to ensure all _ids | |
| 2103 | // are looked up, but only if the path wasn't modified which | |
| 2104 | // signifies the users intent of the state of the path. | |
| 2105 | 0 | ret = doc.populated(path); |
| 2106 | } | |
| 2107 | ||
| 2108 | 0 | if (!ret || Array.isArray(ret) && 0 === ret.length) { |
| 2109 | 0 | ret = utils.getValue(path, doc); |
| 2110 | } | |
| 2111 | ||
| 2112 | 0 | if (ret) { |
| 2113 | 0 | ret = convertTo_id(ret); |
| 2114 | ||
| 2115 | // previously we always assigned this even if the document had no _id | |
| 2116 | 0 | options._docs[id] = Array.isArray(ret) |
| 2117 | ? ret.slice() | |
| 2118 | : ret; | |
| 2119 | } | |
| 2120 | ||
| 2121 | // always retain original values, even empty values. these are | |
| 2122 | // used to map the query results back to the correct position. | |
| 2123 | 0 | rawIds.push(ret); |
| 2124 | ||
| 2125 | 0 | if (isDocument) { |
| 2126 | // cache original populated _ids and model used | |
| 2127 | 0 | doc.populated(path, options._docs[id], options); |
| 2128 | } | |
| 2129 | } | |
| 2130 | ||
| 2131 | 0 | var ids = utils.array.flatten(rawIds, function (item) { |
| 2132 | // no need to include undefined values in our query | |
| 2133 | 0 | return undefined !== item; |
| 2134 | }); | |
| 2135 | ||
| 2136 | 0 | if (0 === ids.length || ids.every(utils.isNullOrUndefined)) { |
| 2137 | 0 | return cb(); |
| 2138 | } | |
| 2139 | ||
| 2140 | // preserve original match conditions by copying | |
| 2141 | 0 | if (match) { |
| 2142 | 0 | match = utils.object.shallowCopy(match); |
| 2143 | } else { | |
| 2144 | 0 | match = {}; |
| 2145 | } | |
| 2146 | ||
| 2147 | 0 | match._id || (match._id = { $in: ids }); |
| 2148 | ||
| 2149 | 0 | var assignmentOpts = {}; |
| 2150 | 0 | assignmentOpts.sort = options.options && options.options.sort || undefined; |
| 2151 | 0 | assignmentOpts.excludeId = /\s?-_id\s?/.test(select) || (select && 0 === select._id); |
| 2152 | ||
| 2153 | 0 | if (assignmentOpts.excludeId) { |
| 2154 | // override the exclusion from the query so we can use the _id | |
| 2155 | // for document matching during assignment. we'll delete the | |
| 2156 | // _id back off before returning the result. | |
| 2157 | 0 | if ('string' == typeof select) { |
| 2158 | 0 | select = select.replace(/\s?-_id\s?/g, ' '); |
| 2159 | } else { | |
| 2160 | // preserve original select conditions by copying | |
| 2161 | 0 | select = utils.object.shallowCopy(select); |
| 2162 | 0 | delete select._id; |
| 2163 | } | |
| 2164 | } | |
| 2165 | ||
| 2166 | // if a limit option is passed, we should have the limit apply to *each* | |
| 2167 | // document, not apply in the aggregate | |
| 2168 | 0 | if (options.options && options.options.limit) { |
| 2169 | 0 | options.options.limit = options.options.limit * len; |
| 2170 | } | |
| 2171 | ||
| 2172 | 0 | Model.find(match, select, options.options, function (err, vals) { |
| 2173 | 0 | if (err) return cb(err); |
| 2174 | ||
| 2175 | 0 | var lean = options.options && options.options.lean; |
| 2176 | 0 | var len = vals.length; |
| 2177 | 0 | var rawOrder = {}; |
| 2178 | 0 | var rawDocs = {} |
| 2179 | 0 | var key; |
| 2180 | 0 | var val; |
| 2181 | ||
| 2182 | // optimization: | |
| 2183 | // record the document positions as returned by | |
| 2184 | // the query result. | |
| 2185 | 0 | for (var i = 0; i < len; i++) { |
| 2186 | 0 | val = vals[i]; |
| 2187 | 0 | key = String(utils.getValue('_id', val)); |
| 2188 | 0 | rawDocs[key] = val; |
| 2189 | 0 | rawOrder[key] = i; |
| 2190 | ||
| 2191 | // flag each as result of population | |
| 2192 | 0 | if (!lean) val.$__.wasPopulated = true; |
| 2193 | } | |
| 2194 | ||
| 2195 | 0 | assignVals({ |
| 2196 | rawIds: rawIds, | |
| 2197 | rawDocs: rawDocs, | |
| 2198 | rawOrder: rawOrder, | |
| 2199 | docs: docs, | |
| 2200 | path: path, | |
| 2201 | options: assignmentOpts | |
| 2202 | }); | |
| 2203 | ||
| 2204 | 0 | cb(); |
| 2205 | }); | |
| 2206 | } | |
| 2207 | ||
| 2208 | /*! | |
| 2209 | * Retrieve the _id of `val` if a Document or Array of Documents. | |
| 2210 | * | |
| 2211 | * @param {Array|Document|Any} val | |
| 2212 | * @return {Array|Document|Any} | |
| 2213 | */ | |
| 2214 | ||
| 2215 | 1 | function convertTo_id (val) { |
| 2216 | 0 | if (val instanceof Model) return val._id; |
| 2217 | ||
| 2218 | 0 | if (Array.isArray(val)) { |
| 2219 | 0 | for (var i = 0; i < val.length; ++i) { |
| 2220 | 0 | if (val[i] instanceof Model) { |
| 2221 | 0 | val[i] = val[i]._id; |
| 2222 | } | |
| 2223 | } | |
| 2224 | 0 | return val; |
| 2225 | } | |
| 2226 | ||
| 2227 | 0 | return val; |
| 2228 | } | |
| 2229 | ||
| 2230 | /*! | |
| 2231 | * Assigns documents returned from a population query back | |
| 2232 | * to the original document path. | |
| 2233 | */ | |
| 2234 | ||
| 2235 | 1 | function assignVals (o) { |
| 2236 | // replace the original ids in our intermediate _ids structure | |
| 2237 | // with the documents found by query | |
| 2238 | ||
| 2239 | 0 | assignRawDocsToIdStructure(o.rawIds, o.rawDocs, o.rawOrder, o.options); |
| 2240 | ||
| 2241 | // now update the original documents being populated using the | |
| 2242 | // result structure that contains real documents. | |
| 2243 | ||
| 2244 | 0 | var docs = o.docs; |
| 2245 | 0 | var path = o.path; |
| 2246 | 0 | var rawIds = o.rawIds; |
| 2247 | 0 | var options = o.options; |
| 2248 | ||
| 2249 | 0 | for (var i = 0; i < docs.length; ++i) { |
| 2250 | 0 | utils.setValue(path, rawIds[i], docs[i], function (val) { |
| 2251 | 0 | return valueFilter(val, options); |
| 2252 | }); | |
| 2253 | } | |
| 2254 | } | |
| 2255 | ||
| 2256 | /*! | |
| 2257 | * 1) Apply backwards compatible find/findOne behavior to sub documents | |
| 2258 | * | |
| 2259 | * find logic: | |
| 2260 | * a) filter out non-documents | |
| 2261 | * b) remove _id from sub docs when user specified | |
| 2262 | * | |
| 2263 | * findOne | |
| 2264 | * a) if no doc found, set to null | |
| 2265 | * b) remove _id from sub docs when user specified | |
| 2266 | * | |
| 2267 | * 2) Remove _ids when specified by users query. | |
| 2268 | * | |
| 2269 | * background: | |
| 2270 | * _ids are left in the query even when user excludes them so | |
| 2271 | * that population mapping can occur. | |
| 2272 | */ | |
| 2273 | ||
| 2274 | 1 | function valueFilter (val, assignmentOpts) { |
| 2275 | 0 | if (Array.isArray(val)) { |
| 2276 | // find logic | |
| 2277 | 0 | var ret = []; |
| 2278 | 0 | for (var i = 0; i < val.length; ++i) { |
| 2279 | 0 | var subdoc = val[i]; |
| 2280 | 0 | if (!isDoc(subdoc)) continue; |
| 2281 | 0 | maybeRemoveId(subdoc, assignmentOpts); |
| 2282 | 0 | ret.push(subdoc); |
| 2283 | } | |
| 2284 | 0 | return ret; |
| 2285 | } | |
| 2286 | ||
| 2287 | // findOne | |
| 2288 | 0 | if (isDoc(val)) { |
| 2289 | 0 | maybeRemoveId(val, assignmentOpts); |
| 2290 | 0 | return val; |
| 2291 | } | |
| 2292 | ||
| 2293 | 0 | return null; |
| 2294 | } | |
| 2295 | ||
| 2296 | /*! | |
| 2297 | * Remove _id from `subdoc` if user specified "lean" query option | |
| 2298 | */ | |
| 2299 | ||
| 2300 | 1 | function maybeRemoveId (subdoc, assignmentOpts) { |
| 2301 | 0 | if (assignmentOpts.excludeId) { |
| 2302 | 0 | if ('function' == typeof subdoc.setValue) { |
| 2303 | 0 | subdoc.setValue('_id', undefined); |
| 2304 | } else { | |
| 2305 | 0 | delete subdoc._id; |
| 2306 | } | |
| 2307 | } | |
| 2308 | } | |
| 2309 | ||
| 2310 | /*! | |
| 2311 | * Determine if `doc` is a document returned | |
| 2312 | * by a populate query. | |
| 2313 | */ | |
| 2314 | ||
| 2315 | 1 | function isDoc (doc) { |
| 2316 | 0 | if (null == doc) |
| 2317 | 0 | return false; |
| 2318 | ||
| 2319 | 0 | var type = typeof doc; |
| 2320 | 0 | if ('string' == type) |
| 2321 | 0 | return false; |
| 2322 | ||
| 2323 | 0 | if ('number' == type) |
| 2324 | 0 | return false; |
| 2325 | ||
| 2326 | 0 | if (Buffer.isBuffer(doc)) |
| 2327 | 0 | return false; |
| 2328 | ||
| 2329 | 0 | if ('ObjectID' == doc.constructor.name) |
| 2330 | 0 | return false; |
| 2331 | ||
| 2332 | // only docs | |
| 2333 | 0 | return true; |
| 2334 | } | |
| 2335 | ||
| 2336 | /*! | |
| 2337 | * Assign `vals` returned by mongo query to the `rawIds` | |
| 2338 | * structure returned from utils.getVals() honoring | |
| 2339 | * query sort order if specified by user. | |
| 2340 | * | |
| 2341 | * This can be optimized. | |
| 2342 | * | |
| 2343 | * Rules: | |
| 2344 | * | |
| 2345 | * if the value of the path is not an array, use findOne rules, else find. | |
| 2346 | * for findOne the results are assigned directly to doc path (including null results). | |
| 2347 | * for find, if user specified sort order, results are assigned directly | |
| 2348 | * else documents are put back in original order of array if found in results | |
| 2349 | * | |
| 2350 | * @param {Array} rawIds | |
| 2351 | * @param {Array} vals | |
| 2352 | * @param {Boolean} sort | |
| 2353 | * @api private | |
| 2354 | */ | |
| 2355 | ||
| 2356 | 1 | function assignRawDocsToIdStructure (rawIds, resultDocs, resultOrder, options, recursed) { |
| 2357 | // honor user specified sort order | |
| 2358 | 0 | var newOrder = []; |
| 2359 | 0 | var sorting = options.sort && rawIds.length > 1; |
| 2360 | 0 | var found; |
| 2361 | 0 | var doc; |
| 2362 | 0 | var sid; |
| 2363 | 0 | var id; |
| 2364 | ||
| 2365 | 0 | for (var i = 0; i < rawIds.length; ++i) { |
| 2366 | 0 | id = rawIds[i]; |
| 2367 | ||
| 2368 | 0 | if (Array.isArray(id)) { |
| 2369 | // handle [ [id0, id2], [id3] ] | |
| 2370 | 0 | assignRawDocsToIdStructure(id, resultDocs, resultOrder, options, true); |
| 2371 | 0 | newOrder.push(id); |
| 2372 | 0 | continue; |
| 2373 | } | |
| 2374 | ||
| 2375 | 0 | if (null === id && !sorting) { |
| 2376 | // keep nulls for findOne unless sorting, which always | |
| 2377 | // removes them (backward compat) | |
| 2378 | 0 | newOrder.push(id); |
| 2379 | 0 | continue; |
| 2380 | } | |
| 2381 | ||
| 2382 | 0 | sid = String(id); |
| 2383 | 0 | found = false; |
| 2384 | ||
| 2385 | 0 | if (recursed) { |
| 2386 | // apply find behavior | |
| 2387 | ||
| 2388 | // assign matching documents in original order unless sorting | |
| 2389 | 0 | doc = resultDocs[sid]; |
| 2390 | 0 | if (doc) { |
| 2391 | 0 | if (sorting) { |
| 2392 | 0 | newOrder[resultOrder[sid]] = doc; |
| 2393 | } else { | |
| 2394 | 0 | newOrder.push(doc); |
| 2395 | } | |
| 2396 | } else { | |
| 2397 | 0 | newOrder.push(id); |
| 2398 | } | |
| 2399 | } else { | |
| 2400 | // apply findOne behavior - if document in results, assign, else assign null | |
| 2401 | 0 | newOrder[i] = doc = resultDocs[sid] || null; |
| 2402 | } | |
| 2403 | } | |
| 2404 | ||
| 2405 | 0 | rawIds.length = 0; |
| 2406 | 0 | if (newOrder.length) { |
| 2407 | // reassign the documents based on corrected order | |
| 2408 | ||
| 2409 | // forEach skips over sparse entries in arrays so we | |
| 2410 | // can safely use this to our advantage dealing with sorted | |
| 2411 | // result sets too. | |
| 2412 | 0 | newOrder.forEach(function (doc, i) { |
| 2413 | 0 | rawIds[i] = doc; |
| 2414 | }); | |
| 2415 | } | |
| 2416 | } | |
| 2417 | ||
| 2418 | /** | |
| 2419 | * Finds the schema for `path`. This is different than | |
| 2420 | * calling `schema.path` as it also resolves paths with | |
| 2421 | * positional selectors (something.$.another.$.path). | |
| 2422 | * | |
| 2423 | * @param {String} path | |
| 2424 | * @return {Schema} | |
| 2425 | * @api private | |
| 2426 | */ | |
| 2427 | ||
| 2428 | 1 | Model._getSchema = function _getSchema (path) { |
| 2429 | 0 | var schema = this.schema |
| 2430 | , pathschema = schema.path(path); | |
| 2431 | ||
| 2432 | 0 | if (pathschema) |
| 2433 | 0 | return pathschema; |
| 2434 | ||
| 2435 | // look for arrays | |
| 2436 | 0 | return (function search (parts, schema) { |
| 2437 | 0 | var p = parts.length + 1 |
| 2438 | , foundschema | |
| 2439 | , trypath | |
| 2440 | ||
| 2441 | 0 | while (p--) { |
| 2442 | 0 | trypath = parts.slice(0, p).join('.'); |
| 2443 | 0 | foundschema = schema.path(trypath); |
| 2444 | 0 | if (foundschema) { |
| 2445 | 0 | if (foundschema.caster) { |
| 2446 | ||
| 2447 | // array of Mixed? | |
| 2448 | 0 | if (foundschema.caster instanceof Types.Mixed) { |
| 2449 | 0 | return foundschema.caster; |
| 2450 | } | |
| 2451 | ||
| 2452 | // Now that we found the array, we need to check if there | |
| 2453 | // are remaining document paths to look up for casting. | |
| 2454 | // Also we need to handle array.$.path since schema.path | |
| 2455 | // doesn't work for that. | |
| 2456 | // If there is no foundschema.schema we are dealing with | |
| 2457 | // a path like array.$ | |
| 2458 | 0 | if (p !== parts.length && foundschema.schema) { |
| 2459 | 0 | if ('$' === parts[p]) { |
| 2460 | // comments.$.comments.$.title | |
| 2461 | 0 | return search(parts.slice(p+1), foundschema.schema); |
| 2462 | } else { | |
| 2463 | // this is the last path of the selector | |
| 2464 | 0 | return search(parts.slice(p), foundschema.schema); |
| 2465 | } | |
| 2466 | } | |
| 2467 | } | |
| 2468 | 0 | return foundschema; |
| 2469 | } | |
| 2470 | } | |
| 2471 | })(path.split('.'), schema) | |
| 2472 | } | |
| 2473 | ||
| 2474 | /*! | |
| 2475 | * Compiler utility. | |
| 2476 | * | |
| 2477 | * @param {String} name model name | |
| 2478 | * @param {Schema} schema | |
| 2479 | * @param {String} collectionName | |
| 2480 | * @param {Connection} connection | |
| 2481 | * @param {Mongoose} base mongoose instance | |
| 2482 | */ | |
| 2483 | ||
| 2484 | 1 | Model.compile = function compile (name, schema, collectionName, connection, base) { |
| 2485 | 0 | var versioningEnabled = false !== schema.options.versionKey; |
| 2486 | ||
| 2487 | 0 | if (versioningEnabled && !schema.paths[schema.options.versionKey]) { |
| 2488 | // add versioning to top level documents only | |
| 2489 | 0 | var o = {}; |
| 2490 | 0 | o[schema.options.versionKey] = Number; |
| 2491 | 0 | schema.add(o); |
| 2492 | } | |
| 2493 | ||
| 2494 | // generate new class | |
| 2495 | 0 | function model (doc, fields, skipId) { |
| 2496 | 0 | if (!(this instanceof model)) |
| 2497 | 0 | return new model(doc, fields, skipId); |
| 2498 | 0 | Model.call(this, doc, fields, skipId); |
| 2499 | }; | |
| 2500 | ||
| 2501 | 0 | model.base = base; |
| 2502 | 0 | model.modelName = name; |
| 2503 | 0 | model.__proto__ = Model; |
| 2504 | 0 | model.prototype.__proto__ = Model.prototype; |
| 2505 | 0 | model.model = Model.prototype.model; |
| 2506 | 0 | model.db = model.prototype.db = connection; |
| 2507 | 0 | model.discriminators = model.prototype.discriminators = undefined; |
| 2508 | ||
| 2509 | 0 | model.prototype.$__setSchema(schema); |
| 2510 | ||
| 2511 | 0 | var collectionOptions = { |
| 2512 | bufferCommands: schema.options.bufferCommands | |
| 2513 | , capped: schema.options.capped | |
| 2514 | }; | |
| 2515 | ||
| 2516 | 0 | model.prototype.collection = connection.collection( |
| 2517 | collectionName | |
| 2518 | , collectionOptions | |
| 2519 | ); | |
| 2520 | ||
| 2521 | // apply methods | |
| 2522 | 0 | for (var i in schema.methods) |
| 2523 | 0 | model.prototype[i] = schema.methods[i]; |
| 2524 | ||
| 2525 | // apply statics | |
| 2526 | 0 | for (var i in schema.statics) |
| 2527 | 0 | model[i] = schema.statics[i]; |
| 2528 | ||
| 2529 | 0 | model.schema = model.prototype.schema; |
| 2530 | 0 | model.options = model.prototype.options; |
| 2531 | 0 | model.collection = model.prototype.collection; |
| 2532 | ||
| 2533 | 0 | return model; |
| 2534 | }; | |
| 2535 | ||
| 2536 | /*! | |
| 2537 | * Subclass this model with `conn`, `schema`, and `collection` settings. | |
| 2538 | * | |
| 2539 | * @param {Connection} conn | |
| 2540 | * @param {Schema} [schema] | |
| 2541 | * @param {String} [collection] | |
| 2542 | * @return {Model} | |
| 2543 | */ | |
| 2544 | ||
| 2545 | 1 | Model.__subclass = function subclass (conn, schema, collection) { |
| 2546 | // subclass model using this connection and collection name | |
| 2547 | 0 | var model = this; |
| 2548 | ||
| 2549 | 0 | var Model = function Model (doc, fields, skipId) { |
| 2550 | 0 | if (!(this instanceof Model)) { |
| 2551 | 0 | return new Model(doc, fields, skipId); |
| 2552 | } | |
| 2553 | 0 | model.call(this, doc, fields, skipId); |
| 2554 | } | |
| 2555 | ||
| 2556 | 0 | Model.__proto__ = model; |
| 2557 | 0 | Model.prototype.__proto__ = model.prototype; |
| 2558 | 0 | Model.db = Model.prototype.db = conn; |
| 2559 | ||
| 2560 | 0 | var s = schema && 'string' != typeof schema |
| 2561 | ? schema | |
| 2562 | : model.prototype.schema; | |
| 2563 | ||
| 2564 | 0 | var options = s.options || {}; |
| 2565 | ||
| 2566 | 0 | if (!collection) { |
| 2567 | 0 | collection = model.prototype.schema.get('collection') |
| 2568 | || utils.toCollectionName(model.modelName, options); | |
| 2569 | } | |
| 2570 | ||
| 2571 | 0 | var collectionOptions = { |
| 2572 | bufferCommands: s ? options.bufferCommands : true | |
| 2573 | , capped: s && options.capped | |
| 2574 | }; | |
| 2575 | ||
| 2576 | 0 | Model.prototype.collection = conn.collection(collection, collectionOptions); |
| 2577 | 0 | Model.collection = Model.prototype.collection; |
| 2578 | 0 | Model.init(); |
| 2579 | 0 | return Model; |
| 2580 | } | |
| 2581 | ||
| 2582 | /*! | |
| 2583 | * Module exports. | |
| 2584 | */ | |
| 2585 | ||
| 2586 | 1 | module.exports = exports = Model; |
| 2587 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MPromise = require('mpromise'); |
| 7 | ||
| 8 | /** | |
| 9 | * Promise constructor. | |
| 10 | * | |
| 11 | * Promises are returned from executed queries. Example: | |
| 12 | * | |
| 13 | * var query = Candy.find({ bar: true }); | |
| 14 | * var promise = query.exec(); | |
| 15 | * | |
| 16 | * @param {Function} fn a function which will be called when the promise is resolved that accepts `fn(err, ...){}` as signature | |
| 17 | * @inherits mpromise https://github.com/aheckmann/mpromise | |
| 18 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter | |
| 19 | * @event `err`: Emits when the promise is rejected | |
| 20 | * @event `complete`: Emits when the promise is fulfilled | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | function Promise (fn) { |
| 25 | 0 | MPromise.call(this, fn); |
| 26 | } | |
| 27 | ||
| 28 | /*! | |
| 29 | * Inherit from mpromise | |
| 30 | */ | |
| 31 | ||
| 32 | 1 | Promise.prototype = Object.create(MPromise.prototype, { |
| 33 | constructor: { | |
| 34 | value: Promise | |
| 35 | , enumerable: false | |
| 36 | , writable: true | |
| 37 | , configurable: true | |
| 38 | } | |
| 39 | }); | |
| 40 | ||
| 41 | /*! | |
| 42 | * Override event names for backward compatibility. | |
| 43 | */ | |
| 44 | ||
| 45 | 1 | Promise.SUCCESS = 'complete'; |
| 46 | 1 | Promise.FAILURE = 'err'; |
| 47 | ||
| 48 | /** | |
| 49 | * Adds `listener` to the `event`. | |
| 50 | * | |
| 51 | * If `event` is either the success or failure event and the event has already been emitted, the`listener` is called immediately and passed the results of the original emitted event. | |
| 52 | * | |
| 53 | * @see mpromise#on https://github.com/aheckmann/mpromise#on | |
| 54 | * @method on | |
| 55 | * @memberOf Promise | |
| 56 | * @param {String} event | |
| 57 | * @param {Function} listener | |
| 58 | * @return {Promise} this | |
| 59 | * @api public | |
| 60 | */ | |
| 61 | ||
| 62 | /** | |
| 63 | * Rejects this promise with `reason`. | |
| 64 | * | |
| 65 | * If the promise has already been fulfilled or rejected, not action is taken. | |
| 66 | * | |
| 67 | * @see mpromise#reject https://github.com/aheckmann/mpromise#reject | |
| 68 | * @method reject | |
| 69 | * @memberOf Promise | |
| 70 | * @param {Object|String|Error} reason | |
| 71 | * @return {Promise} this | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | ||
| 75 | /** | |
| 76 | * Rejects this promise with `err`. | |
| 77 | * | |
| 78 | * If the promise has already been fulfilled or rejected, not action is taken. | |
| 79 | * | |
| 80 | * Differs from [#reject](#promise_Promise-reject) by first casting `err` to an `Error` if it is not `instanceof Error`. | |
| 81 | * | |
| 82 | * @api public | |
| 83 | * @param {Error|String} err | |
| 84 | * @return {Promise} this | |
| 85 | */ | |
| 86 | ||
| 87 | 1 | Promise.prototype.error = function (err) { |
| 88 | 0 | if (!(err instanceof Error)) err = new Error(err); |
| 89 | 0 | return this.reject(err); |
| 90 | } | |
| 91 | ||
| 92 | /** | |
| 93 | * Resolves this promise to a rejected state if `err` is passed or a fulfilled state if no `err` is passed. | |
| 94 | * | |
| 95 | * If the promise has already been fulfilled or rejected, not action is taken. | |
| 96 | * | |
| 97 | * `err` will be cast to an Error if not already instanceof Error. | |
| 98 | * | |
| 99 | * _NOTE: overrides [mpromise#resolve](https://github.com/aheckmann/mpromise#resolve) to provide error casting._ | |
| 100 | * | |
| 101 | * @param {Error} [err] error or null | |
| 102 | * @param {Object} [val] value to fulfill the promise with | |
| 103 | * @api public | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | Promise.prototype.resolve = function (err, val) { |
| 107 | 0 | if (err) return this.error(err); |
| 108 | 0 | return this.fulfill(val); |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Adds a single function as a listener to both err and complete. | |
| 113 | * | |
| 114 | * It will be executed with traditional node.js argument position when the promise is resolved. | |
| 115 | * | |
| 116 | * promise.addBack(function (err, args...) { | |
| 117 | * if (err) return handleError(err); | |
| 118 | * console.log('success'); | |
| 119 | * }) | |
| 120 | * | |
| 121 | * Alias of [mpromise#onResolve](https://github.com/aheckmann/mpromise#onresolve). | |
| 122 | * | |
| 123 | * _Deprecated. Use `onResolve` instead._ | |
| 124 | * | |
| 125 | * @method addBack | |
| 126 | * @param {Function} listener | |
| 127 | * @return {Promise} this | |
| 128 | * @deprecated | |
| 129 | */ | |
| 130 | ||
| 131 | 1 | Promise.prototype.addBack = Promise.prototype.onResolve; |
| 132 | ||
| 133 | /** | |
| 134 | * Fulfills this promise with passed arguments. | |
| 135 | * | |
| 136 | * @method fulfill | |
| 137 | * @see https://github.com/aheckmann/mpromise#fulfill | |
| 138 | * @param {any} args | |
| 139 | * @api public | |
| 140 | */ | |
| 141 | ||
| 142 | /** | |
| 143 | * Fulfills this promise with passed arguments. | |
| 144 | * | |
| 145 | * @method fulfill | |
| 146 | * @see https://github.com/aheckmann/mpromise#fulfill | |
| 147 | * @param {any} args | |
| 148 | * @api public | |
| 149 | */ | |
| 150 | ||
| 151 | /** | |
| 152 | * Fulfills this promise with passed arguments. | |
| 153 | * | |
| 154 | * Alias of [mpromise#fulfill](https://github.com/aheckmann/mpromise#fulfill). | |
| 155 | * | |
| 156 | * _Deprecated. Use `fulfill` instead._ | |
| 157 | * | |
| 158 | * @method complete | |
| 159 | * @param {any} args | |
| 160 | * @api public | |
| 161 | * @deprecated | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | Promise.prototype.complete = MPromise.prototype.fulfill; |
| 165 | ||
| 166 | /** | |
| 167 | * Adds a listener to the `complete` (success) event. | |
| 168 | * | |
| 169 | * Alias of [mpromise#onFulfill](https://github.com/aheckmann/mpromise#onfulfill). | |
| 170 | * | |
| 171 | * _Deprecated. Use `onFulfill` instead._ | |
| 172 | * | |
| 173 | * @method addCallback | |
| 174 | * @param {Function} listener | |
| 175 | * @return {Promise} this | |
| 176 | * @api public | |
| 177 | * @deprecated | |
| 178 | */ | |
| 179 | ||
| 180 | 1 | Promise.prototype.addCallback = Promise.prototype.onFulfill; |
| 181 | ||
| 182 | /** | |
| 183 | * Adds a listener to the `err` (rejected) event. | |
| 184 | * | |
| 185 | * Alias of [mpromise#onReject](https://github.com/aheckmann/mpromise#onreject). | |
| 186 | * | |
| 187 | * _Deprecated. Use `onReject` instead._ | |
| 188 | * | |
| 189 | * @method addErrback | |
| 190 | * @param {Function} listener | |
| 191 | * @return {Promise} this | |
| 192 | * @api public | |
| 193 | * @deprecated | |
| 194 | */ | |
| 195 | ||
| 196 | 1 | Promise.prototype.addErrback = Promise.prototype.onReject; |
| 197 | ||
| 198 | /** | |
| 199 | * Creates a new promise and returns it. If `onFulfill` or `onReject` are passed, they are added as SUCCESS/ERROR callbacks to this promise after the nextTick. | |
| 200 | * | |
| 201 | * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. | |
| 202 | * | |
| 203 | * ####Example: | |
| 204 | * | |
| 205 | * var promise = Meetups.find({ tags: 'javascript' }).select('_id').exec(); | |
| 206 | * promise.then(function (meetups) { | |
| 207 | * var ids = meetups.map(function (m) { | |
| 208 | * return m._id; | |
| 209 | * }); | |
| 210 | * return People.find({ meetups: { $in: ids }).exec(); | |
| 211 | * }).then(function (people) { | |
| 212 | * if (people.length < 10000) { | |
| 213 | * throw new Error('Too few people!!!'); | |
| 214 | * } else { | |
| 215 | * throw new Error('Still need more people!!!'); | |
| 216 | * } | |
| 217 | * }).then(null, function (err) { | |
| 218 | * assert.ok(err instanceof Error); | |
| 219 | * }); | |
| 220 | * | |
| 221 | * @see promises-A+ https://github.com/promises-aplus/promises-spec | |
| 222 | * @see mpromise#then https://github.com/aheckmann/mpromise#then | |
| 223 | * @method then | |
| 224 | * @memberOf Promise | |
| 225 | * @param {Function} onFulFill | |
| 226 | * @param {Function} onReject | |
| 227 | * @return {Promise} newPromise | |
| 228 | */ | |
| 229 | ||
| 230 | /** | |
| 231 | * Signifies that this promise was the last in a chain of `then()s`: if a handler passed to the call to `then` which produced this promise throws, the exception will go uncaught. | |
| 232 | * | |
| 233 | * ####Example: | |
| 234 | * | |
| 235 | * var p = new Promise; | |
| 236 | * p.then(function(){ throw new Error('shucks') }); | |
| 237 | * setTimeout(function () { | |
| 238 | * p.fulfill(); | |
| 239 | * // error was caught and swallowed by the promise returned from | |
| 240 | * // p.then(). we either have to always register handlers on | |
| 241 | * // the returned promises or we can do the following... | |
| 242 | * }, 10); | |
| 243 | * | |
| 244 | * // this time we use .end() which prevents catching thrown errors | |
| 245 | * var p = new Promise; | |
| 246 | * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <-- | |
| 247 | * setTimeout(function () { | |
| 248 | * p.fulfill(); // throws "shucks" | |
| 249 | * }, 10); | |
| 250 | * | |
| 251 | * @api public | |
| 252 | * @see mpromise#end https://github.com/aheckmann/mpromise#end | |
| 253 | * @method end | |
| 254 | * @memberOf Promise | |
| 255 | */ | |
| 256 | ||
| 257 | /*! | |
| 258 | * expose | |
| 259 | */ | |
| 260 | ||
| 261 | 1 | module.exports = Promise; |
| 262 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var mquery = require('mquery'); |
| 6 | 1 | var util = require('util'); |
| 7 | 1 | var events = require('events'); |
| 8 | 1 | var mongo = require('mongodb'); |
| 9 | ||
| 10 | 1 | var utils = require('./utils'); |
| 11 | 1 | var Promise = require('./promise'); |
| 12 | 1 | var helpers = require('./queryhelpers'); |
| 13 | 1 | var Types = require('./schema/index'); |
| 14 | 1 | var Document = require('./document'); |
| 15 | 1 | var QueryStream = require('./querystream'); |
| 16 | ||
| 17 | /** | |
| 18 | * Query constructor used for building queries. | |
| 19 | * | |
| 20 | * ####Example: | |
| 21 | * | |
| 22 | * var query = new Query(); | |
| 23 | * query.setOptions({ lean : true }); | |
| 24 | * query.collection(model.collection); | |
| 25 | * query.where('age').gte(21).exec(callback); | |
| 26 | * | |
| 27 | * @param {Object} [options] | |
| 28 | * @param {Object} [model] | |
| 29 | * @param {Object} [conditions] | |
| 30 | * @param {Object} [collection] Mongoose collection | |
| 31 | * @api private | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | function Query(conditions, options, model, collection) { |
| 35 | // this stuff is for dealing with custom queries created by #toConstructor | |
| 36 | 0 | if (!this._mongooseOptions) { |
| 37 | 0 | this._mongooseOptions = options || {}; |
| 38 | } else { | |
| 39 | // this is the case where we have a CustomQuery, we need to check if we got | |
| 40 | // options passed in, and if we did, merge them in | |
| 41 | ||
| 42 | 0 | if (options) { |
| 43 | 0 | var keys = Object.keys(options); |
| 44 | 0 | for (var i=0; i < keys.length; i++) { |
| 45 | 0 | var k = keys[i]; |
| 46 | 0 | this._mongooseOptions[k] = options[k]; |
| 47 | } | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | 0 | if (collection) { |
| 52 | 0 | this.mongooseCollection = collection; |
| 53 | } | |
| 54 | ||
| 55 | 0 | if (model) { |
| 56 | 0 | this.model = model; |
| 57 | } | |
| 58 | ||
| 59 | // this is needed because map reduce returns a model that can be queried, but | |
| 60 | // all of the queries on said model should be lean | |
| 61 | 0 | if (this.model && this.model._mapreduce) { |
| 62 | 0 | this.lean(); |
| 63 | } | |
| 64 | ||
| 65 | // inherit mquery | |
| 66 | 0 | mquery.call(this, this.mongooseCollection, options); |
| 67 | ||
| 68 | 0 | if (conditions) { |
| 69 | 0 | this.find(conditions); |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | /*! | |
| 74 | * inherit mquery | |
| 75 | */ | |
| 76 | ||
| 77 | 1 | Query.prototype = new mquery; |
| 78 | 1 | Query.prototype.constructor = Query; |
| 79 | 1 | Query.base = mquery.prototype; |
| 80 | ||
| 81 | /** | |
| 82 | * Flag to opt out of using `$geoWithin`. | |
| 83 | * | |
| 84 | * mongoose.Query.use$geoWithin = false; | |
| 85 | * | |
| 86 | * MongoDB 2.4 deprecated the use of `$within`, replacing it with `$geoWithin`. Mongoose uses `$geoWithin` by default (which is 100% backward compatible with $within). If you are running an older version of MongoDB, set this flag to `false` so your `within()` queries continue to work. | |
| 87 | * | |
| 88 | * @see http://docs.mongodb.org/manual/reference/operator/geoWithin/ | |
| 89 | * @default true | |
| 90 | * @property use$geoWithin | |
| 91 | * @memberOf Query | |
| 92 | * @receiver Query | |
| 93 | * @api public | |
| 94 | */ | |
| 95 | ||
| 96 | 1 | Query.use$geoWithin = mquery.use$geoWithin; |
| 97 | ||
| 98 | /** | |
| 99 | * Converts this query to a customized, reusable query constructor with all arguments and options retained. | |
| 100 | * | |
| 101 | * ####Example | |
| 102 | * | |
| 103 | * // Create a query for adventure movies and read from the primary | |
| 104 | * // node in the replica-set unless it is down, in which case we'll | |
| 105 | * // read from a secondary node. | |
| 106 | * var query = Movie.find({ tags: 'adventure' }).read('primaryPreferred'); | |
| 107 | * | |
| 108 | * // create a custom Query constructor based off these settings | |
| 109 | * var Adventure = query.toConstructor(); | |
| 110 | * | |
| 111 | * // Adventure is now a subclass of mongoose.Query and works the same way but with the | |
| 112 | * // default query parameters and options set. | |
| 113 | * Adventure().exec(callback) | |
| 114 | * | |
| 115 | * // further narrow down our query results while still using the previous settings | |
| 116 | * Adventure().where({ name: /^Life/ }).exec(callback); | |
| 117 | * | |
| 118 | * // since Adventure is a stand-alone constructor we can also add our own | |
| 119 | * // helper methods and getters without impacting global queries | |
| 120 | * Adventure.prototype.startsWith = function (prefix) { | |
| 121 | * this.where({ name: new RegExp('^' + prefix) }) | |
| 122 | * return this; | |
| 123 | * } | |
| 124 | * Object.defineProperty(Adventure.prototype, 'highlyRated', { | |
| 125 | * get: function () { | |
| 126 | * this.where({ rating: { $gt: 4.5 }}); | |
| 127 | * return this; | |
| 128 | * } | |
| 129 | * }) | |
| 130 | * Adventure().highlyRated.startsWith('Life').exec(callback) | |
| 131 | * | |
| 132 | * New in 3.7.3 | |
| 133 | * | |
| 134 | * @return {Query} subclass-of-Query | |
| 135 | * @api public | |
| 136 | */ | |
| 137 | ||
| 138 | 1 | Query.prototype.toConstructor = function toConstructor () { |
| 139 | 0 | function CustomQuery (criteria, options) { |
| 140 | 0 | if (!(this instanceof CustomQuery)) |
| 141 | 0 | return new CustomQuery(criteria, options); |
| 142 | 0 | Query.call(this, criteria, options || null); |
| 143 | } | |
| 144 | ||
| 145 | 0 | util.inherits(CustomQuery, Query); |
| 146 | ||
| 147 | // set inherited defaults | |
| 148 | 0 | var p = CustomQuery.prototype; |
| 149 | ||
| 150 | 0 | p.options = {}; |
| 151 | ||
| 152 | 0 | p.setOptions(this.options); |
| 153 | ||
| 154 | 0 | p.op = this.op; |
| 155 | 0 | p._conditions = utils.clone(this._conditions); |
| 156 | 0 | p._fields = utils.clone(this._fields); |
| 157 | 0 | p._update = utils.clone(this._update); |
| 158 | 0 | p._path = this._path; |
| 159 | 0 | p._distict = this._distinct; |
| 160 | 0 | p._collection = this._collection; |
| 161 | 0 | p.model = this.model; |
| 162 | 0 | p.mongooseCollection = this.mongooseCollection; |
| 163 | 0 | p._mongooseOptions = this._mongooseOptions; |
| 164 | ||
| 165 | 0 | return CustomQuery; |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Specifies a javascript function or expression to pass to MongoDBs query system. | |
| 170 | * | |
| 171 | * ####Example | |
| 172 | * | |
| 173 | * query.$where('this.comments.length > 10 || this.name.length > 5') | |
| 174 | * | |
| 175 | * // or | |
| 176 | * | |
| 177 | * query.$where(function () { | |
| 178 | * return this.comments.length > 10 || this.name.length > 5; | |
| 179 | * }) | |
| 180 | * | |
| 181 | * ####NOTE: | |
| 182 | * | |
| 183 | * Only use `$where` when you have a condition that cannot be met using other MongoDB operators like `$lt`. | |
| 184 | * **Be sure to read about all of [its caveats](http://docs.mongodb.org/manual/reference/operator/where/) before using.** | |
| 185 | * | |
| 186 | * @see $where http://docs.mongodb.org/manual/reference/operator/where/ | |
| 187 | * @method $where | |
| 188 | * @param {String|Function} js javascript string or function | |
| 189 | * @return {Query} this | |
| 190 | * @memberOf Query | |
| 191 | * @method $where | |
| 192 | * @api public | |
| 193 | */ | |
| 194 | ||
| 195 | /** | |
| 196 | * Specifies a `path` for use with chaining. | |
| 197 | * | |
| 198 | * ####Example | |
| 199 | * | |
| 200 | * // instead of writing: | |
| 201 | * User.find({age: {$gte: 21, $lte: 65}}, callback); | |
| 202 | * | |
| 203 | * // we can instead write: | |
| 204 | * User.where('age').gte(21).lte(65); | |
| 205 | * | |
| 206 | * // passing query conditions is permitted | |
| 207 | * User.find().where({ name: 'vonderful' }) | |
| 208 | * | |
| 209 | * // chaining | |
| 210 | * User | |
| 211 | * .where('age').gte(21).lte(65) | |
| 212 | * .where('name', /^vonderful/i) | |
| 213 | * .where('friends').slice(10) | |
| 214 | * .exec(callback) | |
| 215 | * | |
| 216 | * @method where | |
| 217 | * @memberOf Query | |
| 218 | * @param {String|Object} [path] | |
| 219 | * @param {any} [val] | |
| 220 | * @return {Query} this | |
| 221 | * @api public | |
| 222 | */ | |
| 223 | ||
| 224 | /** | |
| 225 | * Specifies the complementary comparison value for paths specified with `where()` | |
| 226 | * | |
| 227 | * ####Example | |
| 228 | * | |
| 229 | * User.where('age').equals(49); | |
| 230 | * | |
| 231 | * // is the same as | |
| 232 | * | |
| 233 | * User.where('age', 49); | |
| 234 | * | |
| 235 | * @method equals | |
| 236 | * @memberOf Query | |
| 237 | * @param {Object} val | |
| 238 | * @return {Query} this | |
| 239 | * @api public | |
| 240 | */ | |
| 241 | ||
| 242 | /** | |
| 243 | * Specifies arguments for an `$or` condition. | |
| 244 | * | |
| 245 | * ####Example | |
| 246 | * | |
| 247 | * query.or([{ color: 'red' }, { status: 'emergency' }]) | |
| 248 | * | |
| 249 | * @see $or http://docs.mongodb.org/manual/reference/operator/or/ | |
| 250 | * @method or | |
| 251 | * @memberOf Query | |
| 252 | * @param {Array} array array of conditions | |
| 253 | * @return {Query} this | |
| 254 | * @api public | |
| 255 | */ | |
| 256 | ||
| 257 | /** | |
| 258 | * Specifies arguments for a `$nor` condition. | |
| 259 | * | |
| 260 | * ####Example | |
| 261 | * | |
| 262 | * query.nor([{ color: 'green' }, { status: 'ok' }]) | |
| 263 | * | |
| 264 | * @see $nor http://docs.mongodb.org/manual/reference/operator/nor/ | |
| 265 | * @method nor | |
| 266 | * @memberOf Query | |
| 267 | * @param {Array} array array of conditions | |
| 268 | * @return {Query} this | |
| 269 | * @api public | |
| 270 | */ | |
| 271 | ||
| 272 | /** | |
| 273 | * Specifies arguments for a `$and` condition. | |
| 274 | * | |
| 275 | * ####Example | |
| 276 | * | |
| 277 | * query.and([{ color: 'green' }, { status: 'ok' }]) | |
| 278 | * | |
| 279 | * @method and | |
| 280 | * @memberOf Query | |
| 281 | * @see $and http://docs.mongodb.org/manual/reference/operator/and/ | |
| 282 | * @param {Array} array array of conditions | |
| 283 | * @return {Query} this | |
| 284 | * @api public | |
| 285 | */ | |
| 286 | ||
| 287 | /** | |
| 288 | * Specifies a $gt query condition. | |
| 289 | * | |
| 290 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 291 | * | |
| 292 | * ####Example | |
| 293 | * | |
| 294 | * Thing.find().where('age').gt(21) | |
| 295 | * | |
| 296 | * // or | |
| 297 | * Thing.find().gt('age', 21) | |
| 298 | * | |
| 299 | * @method gt | |
| 300 | * @memberOf Query | |
| 301 | * @param {String} [path] | |
| 302 | * @param {Number} val | |
| 303 | * @see $gt http://docs.mongodb.org/manual/reference/operator/gt/ | |
| 304 | * @api public | |
| 305 | */ | |
| 306 | ||
| 307 | /** | |
| 308 | * Specifies a $gte query condition. | |
| 309 | * | |
| 310 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 311 | * | |
| 312 | * @method gte | |
| 313 | * @memberOf Query | |
| 314 | * @param {String} [path] | |
| 315 | * @param {Number} val | |
| 316 | * @see $gte http://docs.mongodb.org/manual/reference/operator/gte/ | |
| 317 | * @api public | |
| 318 | */ | |
| 319 | ||
| 320 | /** | |
| 321 | * Specifies a $lt query condition. | |
| 322 | * | |
| 323 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 324 | * | |
| 325 | * @method lt | |
| 326 | * @memberOf Query | |
| 327 | * @param {String} [path] | |
| 328 | * @param {Number} val | |
| 329 | * @see $lt http://docs.mongodb.org/manual/reference/operator/lt/ | |
| 330 | * @api public | |
| 331 | */ | |
| 332 | ||
| 333 | /** | |
| 334 | * Specifies a $lte query condition. | |
| 335 | * | |
| 336 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 337 | * | |
| 338 | * @method lte | |
| 339 | * @see $lte http://docs.mongodb.org/manual/reference/operator/lte/ | |
| 340 | * @memberOf Query | |
| 341 | * @param {String} [path] | |
| 342 | * @param {Number} val | |
| 343 | * @api public | |
| 344 | */ | |
| 345 | ||
| 346 | /** | |
| 347 | * Specifies a $ne query condition. | |
| 348 | * | |
| 349 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 350 | * | |
| 351 | * @see $ne http://docs.mongodb.org/manual/reference/operator/ne/ | |
| 352 | * @method ne | |
| 353 | * @memberOf Query | |
| 354 | * @param {String} [path] | |
| 355 | * @param {Number} val | |
| 356 | * @api public | |
| 357 | */ | |
| 358 | ||
| 359 | /** | |
| 360 | * Specifies an $in query condition. | |
| 361 | * | |
| 362 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 363 | * | |
| 364 | * @see $in http://docs.mongodb.org/manual/reference/operator/in/ | |
| 365 | * @method in | |
| 366 | * @memberOf Query | |
| 367 | * @param {String} [path] | |
| 368 | * @param {Number} val | |
| 369 | * @api public | |
| 370 | */ | |
| 371 | ||
| 372 | /** | |
| 373 | * Specifies an $nin query condition. | |
| 374 | * | |
| 375 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 376 | * | |
| 377 | * @see $nin http://docs.mongodb.org/manual/reference/operator/nin/ | |
| 378 | * @method nin | |
| 379 | * @memberOf Query | |
| 380 | * @param {String} [path] | |
| 381 | * @param {Number} val | |
| 382 | * @api public | |
| 383 | */ | |
| 384 | ||
| 385 | /** | |
| 386 | * Specifies an $all query condition. | |
| 387 | * | |
| 388 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 389 | * | |
| 390 | * @see $all http://docs.mongodb.org/manual/reference/operator/all/ | |
| 391 | * @method all | |
| 392 | * @memberOf Query | |
| 393 | * @param {String} [path] | |
| 394 | * @param {Number} val | |
| 395 | * @api public | |
| 396 | */ | |
| 397 | ||
| 398 | /** | |
| 399 | * Specifies a $size query condition. | |
| 400 | * | |
| 401 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 402 | * | |
| 403 | * ####Example | |
| 404 | * | |
| 405 | * MyModel.where('tags').size(0).exec(function (err, docs) { | |
| 406 | * if (err) return handleError(err); | |
| 407 | * | |
| 408 | * assert(Array.isArray(docs)); | |
| 409 | * console.log('documents with 0 tags', docs); | |
| 410 | * }) | |
| 411 | * | |
| 412 | * @see $size http://docs.mongodb.org/manual/reference/operator/size/ | |
| 413 | * @method size | |
| 414 | * @memberOf Query | |
| 415 | * @param {String} [path] | |
| 416 | * @param {Number} val | |
| 417 | * @api public | |
| 418 | */ | |
| 419 | ||
| 420 | /** | |
| 421 | * Specifies a $regex query condition. | |
| 422 | * | |
| 423 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 424 | * | |
| 425 | * @see $regex http://docs.mongodb.org/manual/reference/operator/regex/ | |
| 426 | * @method regex | |
| 427 | * @memberOf Query | |
| 428 | * @param {String} [path] | |
| 429 | * @param {Number} val | |
| 430 | * @api public | |
| 431 | */ | |
| 432 | ||
| 433 | /** | |
| 434 | * Specifies a $maxDistance query condition. | |
| 435 | * | |
| 436 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 437 | * | |
| 438 | * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ | |
| 439 | * @method maxDistance | |
| 440 | * @memberOf Query | |
| 441 | * @param {String} [path] | |
| 442 | * @param {Number} val | |
| 443 | * @api public | |
| 444 | */ | |
| 445 | ||
| 446 | /** | |
| 447 | * Specifies a `$mod` condition | |
| 448 | * | |
| 449 | * @method mod | |
| 450 | * @memberOf Query | |
| 451 | * @param {String} [path] | |
| 452 | * @param {Number} val | |
| 453 | * @return {Query} this | |
| 454 | * @see $mod http://docs.mongodb.org/manual/reference/operator/mod/ | |
| 455 | * @api public | |
| 456 | */ | |
| 457 | ||
| 458 | /** | |
| 459 | * Specifies an `$exists` condition | |
| 460 | * | |
| 461 | * ####Example | |
| 462 | * | |
| 463 | * // { name: { $exists: true }} | |
| 464 | * Thing.where('name').exists() | |
| 465 | * Thing.where('name').exists(true) | |
| 466 | * Thing.find().exists('name') | |
| 467 | * | |
| 468 | * // { name: { $exists: false }} | |
| 469 | * Thing.where('name').exists(false); | |
| 470 | * Thing.find().exists('name', false); | |
| 471 | * | |
| 472 | * @method exists | |
| 473 | * @memberOf Query | |
| 474 | * @param {String} [path] | |
| 475 | * @param {Number} val | |
| 476 | * @return {Query} this | |
| 477 | * @see $exists http://docs.mongodb.org/manual/reference/operator/exists/ | |
| 478 | * @api public | |
| 479 | */ | |
| 480 | ||
| 481 | /** | |
| 482 | * Specifies an `$elemMatch` condition | |
| 483 | * | |
| 484 | * ####Example | |
| 485 | * | |
| 486 | * query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}}) | |
| 487 | * | |
| 488 | * query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}}) | |
| 489 | * | |
| 490 | * query.elemMatch('comment', function (elem) { | |
| 491 | * elem.where('author').equals('autobot'); | |
| 492 | * elem.where('votes').gte(5); | |
| 493 | * }) | |
| 494 | * | |
| 495 | * query.where('comment').elemMatch(function (elem) { | |
| 496 | * elem.where({ author: 'autobot' }); | |
| 497 | * elem.where('votes').gte(5); | |
| 498 | * }) | |
| 499 | * | |
| 500 | * @method elemMatch | |
| 501 | * @memberOf Query | |
| 502 | * @param {String|Object|Function} path | |
| 503 | * @param {Object|Function} criteria | |
| 504 | * @return {Query} this | |
| 505 | * @see $elemMatch http://docs.mongodb.org/manual/reference/operator/elemMatch/ | |
| 506 | * @api public | |
| 507 | */ | |
| 508 | ||
| 509 | /** | |
| 510 | * Defines a `$within` or `$geoWithin` argument for geo-spatial queries. | |
| 511 | * | |
| 512 | * ####Example | |
| 513 | * | |
| 514 | * query.where(path).within().box() | |
| 515 | * query.where(path).within().circle() | |
| 516 | * query.where(path).within().geometry() | |
| 517 | * | |
| 518 | * query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true }); | |
| 519 | * query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] }); | |
| 520 | * query.where('loc').within({ polygon: [[],[],[],[]] }); | |
| 521 | * | |
| 522 | * query.where('loc').within([], [], []) // polygon | |
| 523 | * query.where('loc').within([], []) // box | |
| 524 | * query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry | |
| 525 | * | |
| 526 | * **MUST** be used after `where()`. | |
| 527 | * | |
| 528 | * ####NOTE: | |
| 529 | * | |
| 530 | * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](#query_Query-use%2524geoWithin). | |
| 531 | * | |
| 532 | * ####NOTE: | |
| 533 | * | |
| 534 | * In Mongoose 3.7, `within` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within). | |
| 535 | * | |
| 536 | * @method within | |
| 537 | * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/ | |
| 538 | * @see $box http://docs.mongodb.org/manual/reference/operator/box/ | |
| 539 | * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ | |
| 540 | * @see $center http://docs.mongodb.org/manual/reference/operator/center/ | |
| 541 | * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ | |
| 542 | * @memberOf Query | |
| 543 | * @return {Query} this | |
| 544 | * @api public | |
| 545 | */ | |
| 546 | ||
| 547 | /** | |
| 548 | * Specifies a $slice projection for an array. | |
| 549 | * | |
| 550 | * ####Example | |
| 551 | * | |
| 552 | * query.slice('comments', 5) | |
| 553 | * query.slice('comments', -5) | |
| 554 | * query.slice('comments', [10, 5]) | |
| 555 | * query.where('comments').slice(5) | |
| 556 | * query.where('comments').slice([-10, 5]) | |
| 557 | * | |
| 558 | * @method slice | |
| 559 | * @memberOf Query | |
| 560 | * @param {String} [path] | |
| 561 | * @param {Number} val number/range of elements to slice | |
| 562 | * @return {Query} this | |
| 563 | * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements | |
| 564 | * @see $slice http://docs.mongodb.org/manual/reference/projection/slice/#prj._S_slice | |
| 565 | * @api public | |
| 566 | */ | |
| 567 | ||
| 568 | /** | |
| 569 | * Specifies the maximum number of documents the query will return. | |
| 570 | * | |
| 571 | * ####Example | |
| 572 | * | |
| 573 | * query.limit(20) | |
| 574 | * | |
| 575 | * ####Note | |
| 576 | * | |
| 577 | * Cannot be used with `distinct()` | |
| 578 | * | |
| 579 | * @method limit | |
| 580 | * @memberOf Query | |
| 581 | * @param {Number} val | |
| 582 | * @api public | |
| 583 | */ | |
| 584 | ||
| 585 | /** | |
| 586 | * Specifies the number of documents to skip. | |
| 587 | * | |
| 588 | * ####Example | |
| 589 | * | |
| 590 | * query.skip(100).limit(20) | |
| 591 | * | |
| 592 | * ####Note | |
| 593 | * | |
| 594 | * Cannot be used with `distinct()` | |
| 595 | * | |
| 596 | * @method skip | |
| 597 | * @memberOf Query | |
| 598 | * @param {Number} val | |
| 599 | * @see cursor.skip http://docs.mongodb.org/manual/reference/method/cursor.skip/ | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | ||
| 603 | /** | |
| 604 | * Specifies the maxScan option. | |
| 605 | * | |
| 606 | * ####Example | |
| 607 | * | |
| 608 | * query.maxScan(100) | |
| 609 | * | |
| 610 | * ####Note | |
| 611 | * | |
| 612 | * Cannot be used with `distinct()` | |
| 613 | * | |
| 614 | * @method maxScan | |
| 615 | * @memberOf Query | |
| 616 | * @param {Number} val | |
| 617 | * @see maxScan http://docs.mongodb.org/manual/reference/operator/maxScan/ | |
| 618 | * @api public | |
| 619 | */ | |
| 620 | ||
| 621 | /** | |
| 622 | * Specifies the batchSize option. | |
| 623 | * | |
| 624 | * ####Example | |
| 625 | * | |
| 626 | * query.batchSize(100) | |
| 627 | * | |
| 628 | * ####Note | |
| 629 | * | |
| 630 | * Cannot be used with `distinct()` | |
| 631 | * | |
| 632 | * @method batchSize | |
| 633 | * @memberOf Query | |
| 634 | * @param {Number} val | |
| 635 | * @see batchSize http://docs.mongodb.org/manual/reference/method/cursor.batchSize/ | |
| 636 | * @api public | |
| 637 | */ | |
| 638 | ||
| 639 | /** | |
| 640 | * Specifies the `comment` option. | |
| 641 | * | |
| 642 | * ####Example | |
| 643 | * | |
| 644 | * query.comment('login query') | |
| 645 | * | |
| 646 | * ####Note | |
| 647 | * | |
| 648 | * Cannot be used with `distinct()` | |
| 649 | * | |
| 650 | * @method comment | |
| 651 | * @memberOf Query | |
| 652 | * @param {Number} val | |
| 653 | * @see comment http://docs.mongodb.org/manual/reference/operator/comment/ | |
| 654 | * @api public | |
| 655 | */ | |
| 656 | ||
| 657 | /** | |
| 658 | * Specifies this query as a `snapshot` query. | |
| 659 | * | |
| 660 | * ####Example | |
| 661 | * | |
| 662 | * query.snapshot() // true | |
| 663 | * query.snapshot(true) | |
| 664 | * query.snapshot(false) | |
| 665 | * | |
| 666 | * ####Note | |
| 667 | * | |
| 668 | * Cannot be used with `distinct()` | |
| 669 | * | |
| 670 | * @method snapshot | |
| 671 | * @memberOf Query | |
| 672 | * @see snapshot http://docs.mongodb.org/manual/reference/operator/snapshot/ | |
| 673 | * @return {Query} this | |
| 674 | * @api public | |
| 675 | */ | |
| 676 | ||
| 677 | /** | |
| 678 | * Sets query hints. | |
| 679 | * | |
| 680 | * ####Example | |
| 681 | * | |
| 682 | * query.hint({ indexA: 1, indexB: -1}) | |
| 683 | * | |
| 684 | * ####Note | |
| 685 | * | |
| 686 | * Cannot be used with `distinct()` | |
| 687 | * | |
| 688 | * @method hint | |
| 689 | * @memberOf Query | |
| 690 | * @param {Object} val a hint object | |
| 691 | * @return {Query} this | |
| 692 | * @see $hint http://docs.mongodb.org/manual/reference/operator/hint/ | |
| 693 | * @api public | |
| 694 | */ | |
| 695 | ||
| 696 | /** | |
| 697 | * Specifies which document fields to include or exclude | |
| 698 | * | |
| 699 | * When using string syntax, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. Lastly, if a path is prefixed with `+`, it forces inclusion of the path, which is useful for paths excluded at the [schema level](/docs/api.html#schematype_SchemaType-select). | |
| 700 | * | |
| 701 | * ####Example | |
| 702 | * | |
| 703 | * // include a and b, exclude c | |
| 704 | * query.select('a b -c'); | |
| 705 | * | |
| 706 | * // or you may use object notation, useful when | |
| 707 | * // you have keys already prefixed with a "-" | |
| 708 | * query.select({a: 1, b: 1, c: 0}); | |
| 709 | * | |
| 710 | * // force inclusion of field excluded at schema level | |
| 711 | * query.select('+path') | |
| 712 | * | |
| 713 | * ####NOTE: | |
| 714 | * | |
| 715 | * Cannot be used with `distinct()`. | |
| 716 | * | |
| 717 | * _v2 had slightly different syntax such as allowing arrays of field names. This support was removed in v3._ | |
| 718 | * | |
| 719 | * @method select | |
| 720 | * @memberOf Query | |
| 721 | * @param {Object|String} arg | |
| 722 | * @return {Query} this | |
| 723 | * @see SchemaType | |
| 724 | * @api public | |
| 725 | */ | |
| 726 | ||
| 727 | /** | |
| 728 | * _DEPRECATED_ Sets the slaveOk option. | |
| 729 | * | |
| 730 | * **Deprecated** in MongoDB 2.2 in favor of [read preferences](#query_Query-read). | |
| 731 | * | |
| 732 | * ####Example: | |
| 733 | * | |
| 734 | * query.slaveOk() // true | |
| 735 | * query.slaveOk(true) | |
| 736 | * query.slaveOk(false) | |
| 737 | * | |
| 738 | * @method slaveOk | |
| 739 | * @memberOf Query | |
| 740 | * @deprecated use read() preferences instead if on mongodb >= 2.2 | |
| 741 | * @param {Boolean} v defaults to true | |
| 742 | * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference | |
| 743 | * @see slaveOk http://docs.mongodb.org/manual/reference/method/rs.slaveOk/ | |
| 744 | * @see read() #query_Query-read | |
| 745 | * @return {Query} this | |
| 746 | * @api public | |
| 747 | */ | |
| 748 | ||
| 749 | /** | |
| 750 | * Determines the MongoDB nodes from which to read. | |
| 751 | * | |
| 752 | * ####Preferences: | |
| 753 | * | |
| 754 | * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags. | |
| 755 | * secondary Read from secondary if available, otherwise error. | |
| 756 | * primaryPreferred Read from primary if available, otherwise a secondary. | |
| 757 | * secondaryPreferred Read from a secondary if available, otherwise read from the primary. | |
| 758 | * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection. | |
| 759 | * | |
| 760 | * Aliases | |
| 761 | * | |
| 762 | * p primary | |
| 763 | * pp primaryPreferred | |
| 764 | * s secondary | |
| 765 | * sp secondaryPreferred | |
| 766 | * n nearest | |
| 767 | * | |
| 768 | * ####Example: | |
| 769 | * | |
| 770 | * new Query().read('primary') | |
| 771 | * new Query().read('p') // same as primary | |
| 772 | * | |
| 773 | * new Query().read('primaryPreferred') | |
| 774 | * new Query().read('pp') // same as primaryPreferred | |
| 775 | * | |
| 776 | * new Query().read('secondary') | |
| 777 | * new Query().read('s') // same as secondary | |
| 778 | * | |
| 779 | * new Query().read('secondaryPreferred') | |
| 780 | * new Query().read('sp') // same as secondaryPreferred | |
| 781 | * | |
| 782 | * new Query().read('nearest') | |
| 783 | * new Query().read('n') // same as nearest | |
| 784 | * | |
| 785 | * // read from secondaries with matching tags | |
| 786 | * new Query().read('s', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }]) | |
| 787 | * | |
| 788 | * Read more about how to use read preferrences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). | |
| 789 | * | |
| 790 | * @method read | |
| 791 | * @memberOf Query | |
| 792 | * @param {String} pref one of the listed preference options or aliases | |
| 793 | * @param {Array} [tags] optional tags for this query | |
| 794 | * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference | |
| 795 | * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences | |
| 796 | * @return {Query} this | |
| 797 | * @api public | |
| 798 | */ | |
| 799 | ||
| 800 | 1 | Query.prototype.read = function read (pref, tags) { |
| 801 | // first cast into a ReadPreference object to support tags | |
| 802 | 0 | var readPref = utils.readPref.apply(utils.readPref, arguments); |
| 803 | 0 | return Query.base.read.call(this, readPref); |
| 804 | } | |
| 805 | ||
| 806 | /** | |
| 807 | * Merges another Query or conditions object into this one. | |
| 808 | * | |
| 809 | * When a Query is passed, conditions, field selection and options are merged. | |
| 810 | * | |
| 811 | * New in 3.7.0 | |
| 812 | * | |
| 813 | * @method merge | |
| 814 | * @memberOf Query | |
| 815 | * @param {Query|Object} source | |
| 816 | * @return {Query} this | |
| 817 | */ | |
| 818 | ||
| 819 | /** | |
| 820 | * Sets query options. | |
| 821 | * | |
| 822 | * ####Options: | |
| 823 | * | |
| 824 | * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) * | |
| 825 | * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) * | |
| 826 | * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) * | |
| 827 | * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) * | |
| 828 | * - [maxscan](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24maxScan) * | |
| 829 | * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) * | |
| 830 | * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) * | |
| 831 | * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) * | |
| 832 | * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) * | |
| 833 | * - [slaveOk](http://docs.mongodb.org/manual/applications/replication/#read-preference) * | |
| 834 | * - [lean](./api.html#query_Query-lean) * | |
| 835 | * - [safe](http://www.mongodb.org/display/DOCS/getLastError+Command) | |
| 836 | * | |
| 837 | * _* denotes a query helper method is also available_ | |
| 838 | * | |
| 839 | * @param {Object} options | |
| 840 | * @api public | |
| 841 | */ | |
| 842 | ||
| 843 | 1 | Query.prototype.setOptions = function (options, overwrite) { |
| 844 | // overwrite is only for internal use | |
| 845 | 0 | if (overwrite) { |
| 846 | // ensure that _mongooseOptions & options are two different objects | |
| 847 | 0 | this._mongooseOptions = (options && utils.clone(options)) || {}; |
| 848 | 0 | this.options = options || {}; |
| 849 | ||
| 850 | 0 | if('populate' in options) { |
| 851 | 0 | this.populate(this._mongooseOptions); |
| 852 | } | |
| 853 | 0 | return this; |
| 854 | } | |
| 855 | ||
| 856 | 0 | if (!(options && 'Object' == options.constructor.name)) { |
| 857 | 0 | return this; |
| 858 | } | |
| 859 | ||
| 860 | 0 | return Query.base.setOptions.call(this, options); |
| 861 | } | |
| 862 | ||
| 863 | /** | |
| 864 | * Returns fields selection for this query. | |
| 865 | * | |
| 866 | * @method _fieldsForExec | |
| 867 | * @return {Object} | |
| 868 | * @api private | |
| 869 | */ | |
| 870 | ||
| 871 | /** | |
| 872 | * Return an update document with corrected $set operations. | |
| 873 | * | |
| 874 | * @method _updateForExec | |
| 875 | * @api private | |
| 876 | */ | |
| 877 | ||
| 878 | /** | |
| 879 | * Makes sure _path is set. | |
| 880 | * | |
| 881 | * @method _ensurePath | |
| 882 | * @param {String} method | |
| 883 | * @api private | |
| 884 | */ | |
| 885 | ||
| 886 | /** | |
| 887 | * Determines if `conds` can be merged using `mquery().merge()` | |
| 888 | * | |
| 889 | * @method canMerge | |
| 890 | * @memberOf Query | |
| 891 | * @param {Object} conds | |
| 892 | * @return {Boolean} | |
| 893 | * @api private | |
| 894 | */ | |
| 895 | ||
| 896 | /** | |
| 897 | * Returns default options for this query. | |
| 898 | * | |
| 899 | * @param {Model} model | |
| 900 | * @api private | |
| 901 | */ | |
| 902 | ||
| 903 | 1 | Query.prototype._optionsForExec = function (model) { |
| 904 | 0 | var options = Query.base._optionsForExec.call(this); |
| 905 | ||
| 906 | 0 | delete options.populate; |
| 907 | 0 | model = model || this.model; |
| 908 | ||
| 909 | 0 | if (!model) { |
| 910 | 0 | return options; |
| 911 | } else { | |
| 912 | 0 | if (!('safe' in options) && model.schema.options.safe) { |
| 913 | 0 | options.safe = model.schema.options.safe; |
| 914 | } | |
| 915 | ||
| 916 | 0 | if (!('readPreference' in options) && model.schema.options.read) { |
| 917 | 0 | options.readPreference = model.schema.options.read; |
| 918 | } | |
| 919 | ||
| 920 | 0 | return options; |
| 921 | } | |
| 922 | }; | |
| 923 | ||
| 924 | /** | |
| 925 | * Sets the lean option. | |
| 926 | * | |
| 927 | * Documents returned from queries with the `lean` option enabled are plain javascript objects, not [MongooseDocuments](#document-js). They have no `save` method, getters/setters or other Mongoose magic applied. | |
| 928 | * | |
| 929 | * ####Example: | |
| 930 | * | |
| 931 | * new Query().lean() // true | |
| 932 | * new Query().lean(true) | |
| 933 | * new Query().lean(false) | |
| 934 | * | |
| 935 | * Model.find().lean().exec(function (err, docs) { | |
| 936 | * docs[0] instanceof mongoose.Document // false | |
| 937 | * }); | |
| 938 | * | |
| 939 | * This is a [great](https://groups.google.com/forum/#!topic/mongoose-orm/u2_DzDydcnA/discussion) option in high-performance read-only scenarios, especially when combined with [stream](#query_Query-stream). | |
| 940 | * | |
| 941 | * @param {Boolean} bool defaults to true | |
| 942 | * @return {Query} this | |
| 943 | * @api public | |
| 944 | */ | |
| 945 | ||
| 946 | 1 | Query.prototype.lean = function (v) { |
| 947 | 0 | this._mongooseOptions.lean = arguments.length ? !!v : true; |
| 948 | 0 | return this; |
| 949 | } | |
| 950 | ||
| 951 | /** | |
| 952 | * Finds documents. | |
| 953 | * | |
| 954 | * When no `callback` is passed, the query is not executed. | |
| 955 | * | |
| 956 | * ####Example | |
| 957 | * | |
| 958 | * query.find({ name: 'Los Pollos Hermanos' }).find(callback) | |
| 959 | * | |
| 960 | * @param {Object} [criteria] mongodb selector | |
| 961 | * @param {Function} [callback] | |
| 962 | * @return {Query} this | |
| 963 | * @api public | |
| 964 | */ | |
| 965 | ||
| 966 | 1 | Query.prototype.find = function (conditions, callback) { |
| 967 | 0 | if ('function' == typeof conditions) { |
| 968 | 0 | callback = conditions; |
| 969 | 0 | conditions = {}; |
| 970 | 0 | } else if (conditions instanceof Document) { |
| 971 | 0 | conditions = conditions.toObject(); |
| 972 | } | |
| 973 | ||
| 974 | 0 | if (mquery.canMerge(conditions)) { |
| 975 | 0 | this.merge(conditions); |
| 976 | } | |
| 977 | ||
| 978 | 0 | prepareDiscriminatorCriteria(this); |
| 979 | ||
| 980 | 0 | try { |
| 981 | 0 | this.cast(this.model); |
| 982 | 0 | this._castError = null; |
| 983 | } catch (err) { | |
| 984 | 0 | this._castError = err; |
| 985 | } | |
| 986 | ||
| 987 | // if we don't have a callback, then just return the query object | |
| 988 | 0 | if (!callback) { |
| 989 | 0 | return Query.base.find.call(this); |
| 990 | } | |
| 991 | ||
| 992 | 0 | var promise = new Promise(callback); |
| 993 | 0 | if (this._castError) { |
| 994 | 0 | promise.error(this._castError); |
| 995 | 0 | return this; |
| 996 | } | |
| 997 | ||
| 998 | 0 | this._applyPaths(); |
| 999 | 0 | this._fields = this._castFields(this._fields); |
| 1000 | ||
| 1001 | 0 | var fields = this._fieldsForExec(); |
| 1002 | 0 | var options = this._mongooseOptions; |
| 1003 | 0 | var self = this; |
| 1004 | ||
| 1005 | 0 | return Query.base.find.call(this, {}, cb); |
| 1006 | ||
| 1007 | 0 | function cb(err, docs) { |
| 1008 | 0 | if (err) return promise.error(err); |
| 1009 | ||
| 1010 | 0 | if (0 === docs.length) { |
| 1011 | 0 | return promise.complete(docs); |
| 1012 | } | |
| 1013 | ||
| 1014 | 0 | if (!options.populate) { |
| 1015 | 0 | return true === options.lean |
| 1016 | ? promise.complete(docs) | |
| 1017 | : completeMany(self.model, docs, fields, self, null, promise); | |
| 1018 | } | |
| 1019 | ||
| 1020 | 0 | var pop = helpers.preparePopulationOptionsMQ(self, options); |
| 1021 | 0 | self.model.populate(docs, pop, function (err, docs) { |
| 1022 | 0 | if(err) return promise.error(err); |
| 1023 | 0 | return true === options.lean |
| 1024 | ? promise.complete(docs) | |
| 1025 | : completeMany(self.model, docs, fields, self, pop, promise); | |
| 1026 | }); | |
| 1027 | } | |
| 1028 | } | |
| 1029 | ||
| 1030 | /*! | |
| 1031 | * hydrates many documents | |
| 1032 | * | |
| 1033 | * @param {Model} model | |
| 1034 | * @param {Array} docs | |
| 1035 | * @param {Object} fields | |
| 1036 | * @param {Query} self | |
| 1037 | * @param {Array} [pop] array of paths used in population | |
| 1038 | * @param {Promise} promise | |
| 1039 | */ | |
| 1040 | ||
| 1041 | 1 | function completeMany (model, docs, fields, self, pop, promise) { |
| 1042 | 0 | var arr = []; |
| 1043 | 0 | var count = docs.length; |
| 1044 | 0 | var len = count; |
| 1045 | 0 | var opts = pop ? |
| 1046 | { populated: pop } | |
| 1047 | : undefined; | |
| 1048 | 0 | for (var i=0; i < len; ++i) { |
| 1049 | 0 | arr[i] = helpers.createModel(model, docs[i], fields); |
| 1050 | 0 | arr[i].init(docs[i], opts, function (err) { |
| 1051 | 0 | if (err) return promise.error(err); |
| 1052 | 0 | --count || promise.complete(arr); |
| 1053 | }); | |
| 1054 | } | |
| 1055 | } | |
| 1056 | ||
| 1057 | /** | |
| 1058 | * Declares the query a findOne operation. When executed, the first found document is passed to the callback. | |
| 1059 | * | |
| 1060 | * Passing a `callback` executes the query. | |
| 1061 | * | |
| 1062 | * ####Example | |
| 1063 | * | |
| 1064 | * var query = Kitten.where({ color: 'white' }); | |
| 1065 | * query.findOne(function (err, kitten) { | |
| 1066 | * if (err) return handleError(err); | |
| 1067 | * if (kitten) { | |
| 1068 | * // doc may be null if no document matched | |
| 1069 | * } | |
| 1070 | * }); | |
| 1071 | * | |
| 1072 | * @param {Object|Query} [criteria] mongodb selector | |
| 1073 | * @param {Function} [callback] | |
| 1074 | * @return {Query} this | |
| 1075 | * @see findOne http://docs.mongodb.org/manual/reference/method/db.collection.findOne/ | |
| 1076 | * @api public | |
| 1077 | */ | |
| 1078 | ||
| 1079 | 1 | Query.prototype.findOne = function (conditions, fields, options, callback) { |
| 1080 | 0 | if ('function' == typeof conditions) { |
| 1081 | 0 | callback = conditions; |
| 1082 | 0 | conditions = null; |
| 1083 | 0 | fields = null; |
| 1084 | 0 | options = null; |
| 1085 | } | |
| 1086 | ||
| 1087 | 0 | if ('function' == typeof fields) { |
| 1088 | 0 | callback = fields; |
| 1089 | 0 | options = null; |
| 1090 | 0 | fields = null; |
| 1091 | } | |
| 1092 | ||
| 1093 | 0 | if ('function' == typeof options) { |
| 1094 | 0 | callback = options; |
| 1095 | 0 | options = null; |
| 1096 | } | |
| 1097 | ||
| 1098 | // make sure we don't send in the whole Document to merge() | |
| 1099 | 0 | if (conditions instanceof Document) { |
| 1100 | 0 | conditions = conditions.toObject(); |
| 1101 | } | |
| 1102 | ||
| 1103 | 0 | if (options) { |
| 1104 | 0 | this.setOptions(options); |
| 1105 | } | |
| 1106 | ||
| 1107 | 0 | if (fields) { |
| 1108 | 0 | this.select(fields); |
| 1109 | } | |
| 1110 | ||
| 1111 | 0 | if (mquery.canMerge(conditions)) { |
| 1112 | 0 | this.merge(conditions); |
| 1113 | } | |
| 1114 | ||
| 1115 | 0 | prepareDiscriminatorCriteria(this); |
| 1116 | ||
| 1117 | 0 | try { |
| 1118 | 0 | this.cast(this.model); |
| 1119 | 0 | this._castError = null; |
| 1120 | } catch (err) { | |
| 1121 | 0 | this._castError = err; |
| 1122 | } | |
| 1123 | ||
| 1124 | 0 | if (!callback) { |
| 1125 | // already merged in the conditions, don't need to send them in. | |
| 1126 | 0 | return Query.base.findOne.call(this); |
| 1127 | } | |
| 1128 | ||
| 1129 | 0 | var promise = new Promise(callback); |
| 1130 | ||
| 1131 | 0 | if (this._castError) { |
| 1132 | 0 | promise.error(this._castError); |
| 1133 | 0 | return this; |
| 1134 | } | |
| 1135 | ||
| 1136 | 0 | this._applyPaths(); |
| 1137 | 0 | this._fields = this._castFields(this._fields); |
| 1138 | ||
| 1139 | 0 | var options = this._mongooseOptions; |
| 1140 | 0 | var fields = this._fieldsForExec(); |
| 1141 | 0 | var self = this; |
| 1142 | ||
| 1143 | // don't pass in the conditions because we already merged them in | |
| 1144 | 0 | Query.base.findOne.call(this, {}, function cb (err, doc) { |
| 1145 | 0 | if (err) return promise.error(err); |
| 1146 | 0 | if (!doc) return promise.complete(null); |
| 1147 | ||
| 1148 | 0 | if (!options.populate) { |
| 1149 | 0 | return true === options.lean |
| 1150 | ? promise.complete(doc) | |
| 1151 | : completeOne(self.model, doc, fields, self, null, promise); | |
| 1152 | } | |
| 1153 | ||
| 1154 | 0 | var pop = helpers.preparePopulationOptionsMQ(self, options); |
| 1155 | 0 | self.model.populate(doc, pop, function (err, doc) { |
| 1156 | 0 | if (err) return promise.error(err); |
| 1157 | ||
| 1158 | 0 | return true === options.lean |
| 1159 | ? promise.complete(doc) | |
| 1160 | : completeOne(self.model, doc, fields, self, pop, promise); | |
| 1161 | }); | |
| 1162 | }) | |
| 1163 | ||
| 1164 | 0 | return this; |
| 1165 | } | |
| 1166 | ||
| 1167 | /** | |
| 1168 | * Specifying this query as a `count` query. | |
| 1169 | * | |
| 1170 | * Passing a `callback` executes the query. | |
| 1171 | * | |
| 1172 | * ####Example: | |
| 1173 | * | |
| 1174 | * var countQuery = model.where({ 'color': 'black' }).count(); | |
| 1175 | * | |
| 1176 | * query.count({ color: 'black' }).count(callback) | |
| 1177 | * | |
| 1178 | * query.count({ color: 'black' }, callback) | |
| 1179 | * | |
| 1180 | * query.where('color', 'black').count(function (err, count) { | |
| 1181 | * if (err) return handleError(err); | |
| 1182 | * console.log('there are %d kittens', count); | |
| 1183 | * }) | |
| 1184 | * | |
| 1185 | * @param {Object} [criteria] mongodb selector | |
| 1186 | * @param {Function} [callback] | |
| 1187 | * @return {Query} this | |
| 1188 | * @see count http://docs.mongodb.org/manual/reference/method/db.collection.count/ | |
| 1189 | * @api public | |
| 1190 | */ | |
| 1191 | ||
| 1192 | 1 | Query.prototype.count = function (conditions, callback) { |
| 1193 | 0 | if ('function' == typeof conditions) { |
| 1194 | 0 | callback = conditions; |
| 1195 | 0 | conditions = undefined; |
| 1196 | } | |
| 1197 | ||
| 1198 | 0 | if (mquery.canMerge(conditions)) { |
| 1199 | 0 | this.merge(conditions); |
| 1200 | } | |
| 1201 | ||
| 1202 | 0 | try { |
| 1203 | 0 | this.cast(this.model); |
| 1204 | } catch (err) { | |
| 1205 | 0 | callback(err); |
| 1206 | 0 | return this; |
| 1207 | } | |
| 1208 | ||
| 1209 | 0 | return Query.base.count.call(this, {}, callback); |
| 1210 | } | |
| 1211 | ||
| 1212 | /** | |
| 1213 | * Declares or executes a distict() operation. | |
| 1214 | * | |
| 1215 | * Passing a `callback` executes the query. | |
| 1216 | * | |
| 1217 | * ####Example | |
| 1218 | * | |
| 1219 | * distinct(criteria, field, fn) | |
| 1220 | * distinct(criteria, field) | |
| 1221 | * distinct(field, fn) | |
| 1222 | * distinct(field) | |
| 1223 | * distinct(fn) | |
| 1224 | * distinct() | |
| 1225 | * | |
| 1226 | * @param {Object|Query} [criteria] | |
| 1227 | * @param {String} [field] | |
| 1228 | * @param {Function} [callback] | |
| 1229 | * @return {Query} this | |
| 1230 | * @see distinct http://docs.mongodb.org/manual/reference/method/db.collection.distinct/ | |
| 1231 | * @api public | |
| 1232 | */ | |
| 1233 | ||
| 1234 | 1 | Query.prototype.distinct = function (conditions, field, callback) { |
| 1235 | 0 | if (!callback) { |
| 1236 | 0 | if('function' == typeof field) { |
| 1237 | 0 | callback = field; |
| 1238 | 0 | if ('string' == typeof conditions) { |
| 1239 | 0 | field = conditions; |
| 1240 | 0 | conditions = undefined; |
| 1241 | } | |
| 1242 | } | |
| 1243 | ||
| 1244 | 0 | switch (typeof conditions) { |
| 1245 | case 'string': | |
| 1246 | 0 | field = conditions; |
| 1247 | 0 | conditions = undefined; |
| 1248 | 0 | break; |
| 1249 | case 'function': | |
| 1250 | 0 | callback = conditions; |
| 1251 | 0 | field = undefined; |
| 1252 | 0 | conditions = undefined; |
| 1253 | 0 | break; |
| 1254 | } | |
| 1255 | } | |
| 1256 | ||
| 1257 | 0 | if (conditions instanceof Document) { |
| 1258 | 0 | conditions = conditions.toObject(); |
| 1259 | } | |
| 1260 | ||
| 1261 | 0 | if (mquery.canMerge(conditions)) { |
| 1262 | 0 | this.merge(conditions) |
| 1263 | } | |
| 1264 | ||
| 1265 | 0 | try { |
| 1266 | 0 | this.cast(this.model); |
| 1267 | } catch (err) { | |
| 1268 | 0 | callback(err); |
| 1269 | 0 | return this; |
| 1270 | } | |
| 1271 | ||
| 1272 | 0 | return Query.base.distinct.call(this, {}, field, callback); |
| 1273 | } | |
| 1274 | ||
| 1275 | /** | |
| 1276 | * Sets the sort order | |
| 1277 | * | |
| 1278 | * If an object is passed, values allowed are `asc`, `desc`, `ascending`, `descending`, `1`, and `-1`. | |
| 1279 | * | |
| 1280 | * If a string is passed, it must be a space delimited list of path names. The | |
| 1281 | * sort order of each path is ascending unless the path name is prefixed with `-` | |
| 1282 | * which will be treated as descending. | |
| 1283 | * | |
| 1284 | * ####Example | |
| 1285 | * | |
| 1286 | * // sort by "field" ascending and "test" descending | |
| 1287 | * query.sort({ field: 'asc', test: -1 }); | |
| 1288 | * | |
| 1289 | * // equivalent | |
| 1290 | * query.sort('field -test'); | |
| 1291 | * | |
| 1292 | * ####Note | |
| 1293 | * | |
| 1294 | * Cannot be used with `distinct()` | |
| 1295 | * | |
| 1296 | * @param {Object|String} arg | |
| 1297 | * @return {Query} this | |
| 1298 | * @see cursor.sort http://docs.mongodb.org/manual/reference/method/cursor.sort/ | |
| 1299 | * @api public | |
| 1300 | */ | |
| 1301 | ||
| 1302 | 1 | Query.prototype.sort = function (arg) { |
| 1303 | 0 | var nArg = {}; |
| 1304 | ||
| 1305 | 0 | if (arguments.length > 1) { |
| 1306 | 0 | throw new Error("sort() only takes 1 Argument"); |
| 1307 | } | |
| 1308 | ||
| 1309 | 0 | if (Array.isArray(arg)) { |
| 1310 | // time to deal with the terrible syntax | |
| 1311 | 0 | for (var i=0; i < arg.length; i++) { |
| 1312 | 0 | if (!Array.isArray(arg[i])) throw new Error("Invalid sort() argument."); |
| 1313 | 0 | nArg[arg[i][0]] = arg[i][1]; |
| 1314 | } | |
| 1315 | ||
| 1316 | } else { | |
| 1317 | 0 | nArg = arg; |
| 1318 | } | |
| 1319 | ||
| 1320 | 0 | return Query.base.sort.call(this, nArg); |
| 1321 | } | |
| 1322 | ||
| 1323 | /** | |
| 1324 | * Declare and/or execute this query as a remove() operation. | |
| 1325 | * | |
| 1326 | * ####Example | |
| 1327 | * | |
| 1328 | * Model.remove({ artist: 'Anne Murray' }, callback) | |
| 1329 | * | |
| 1330 | * ####Note | |
| 1331 | * | |
| 1332 | * The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call remove() and then execute it by using the `exec()` method. | |
| 1333 | * | |
| 1334 | * // not executed | |
| 1335 | * var query = Model.find().remove({ name: 'Anne Murray' }) | |
| 1336 | * | |
| 1337 | * // executed | |
| 1338 | * query.remove({ name: 'Anne Murray' }, callback) | |
| 1339 | * query.remove({ name: 'Anne Murray' }).remove(callback) | |
| 1340 | * | |
| 1341 | * // executed without a callback (unsafe write) | |
| 1342 | * query.exec() | |
| 1343 | * | |
| 1344 | * // summary | |
| 1345 | * query.remove(conds, fn); // executes | |
| 1346 | * query.remove(conds) | |
| 1347 | * query.remove(fn) // executes | |
| 1348 | * query.remove() | |
| 1349 | * | |
| 1350 | * @param {Object|Query} [criteria] mongodb selector | |
| 1351 | * @param {Function} [callback] | |
| 1352 | * @return {Query} this | |
| 1353 | * @see remove http://docs.mongodb.org/manual/reference/method/db.collection.remove/ | |
| 1354 | * @api public | |
| 1355 | */ | |
| 1356 | ||
| 1357 | 1 | Query.prototype.remove = function (cond, callback) { |
| 1358 | 0 | if ('function' == typeof cond) { |
| 1359 | 0 | callback = cond; |
| 1360 | 0 | cond = null; |
| 1361 | } | |
| 1362 | ||
| 1363 | 0 | var cb = 'function' == typeof callback; |
| 1364 | ||
| 1365 | 0 | try { |
| 1366 | 0 | this.cast(this.model); |
| 1367 | } catch (err) { | |
| 1368 | 0 | if (cb) return process.nextTick(callback.bind(null, err)); |
| 1369 | 0 | return this; |
| 1370 | } | |
| 1371 | ||
| 1372 | 0 | return Query.base.remove.call(this, cond, callback); |
| 1373 | } | |
| 1374 | ||
| 1375 | /*! | |
| 1376 | * hydrates a document | |
| 1377 | * | |
| 1378 | * @param {Model} model | |
| 1379 | * @param {Document} doc | |
| 1380 | * @param {Object} fields | |
| 1381 | * @param {Query} self | |
| 1382 | * @param {Array} [pop] array of paths used in population | |
| 1383 | * @param {Promise} promise | |
| 1384 | */ | |
| 1385 | ||
| 1386 | 1 | function completeOne (model, doc, fields, self, pop, promise) { |
| 1387 | 0 | var opts = pop ? |
| 1388 | { populated: pop } | |
| 1389 | : undefined; | |
| 1390 | ||
| 1391 | 0 | var casted = helpers.createModel(model, doc, fields) |
| 1392 | 0 | casted.init(doc, opts, function (err) { |
| 1393 | 0 | if (err) return promise.error(err); |
| 1394 | 0 | promise.complete(casted); |
| 1395 | }); | |
| 1396 | } | |
| 1397 | ||
| 1398 | /*! | |
| 1399 | * If the model is a discriminator type and not root, then add the key & value to the criteria. | |
| 1400 | */ | |
| 1401 | ||
| 1402 | 1 | function prepareDiscriminatorCriteria(query) { |
| 1403 | 0 | if (!query || !query.model || !query.model.schema) { |
| 1404 | 0 | return; |
| 1405 | } | |
| 1406 | ||
| 1407 | 0 | var schema = query.model.schema; |
| 1408 | ||
| 1409 | 0 | if (schema && schema.discriminatorMapping && !schema.discriminatorMapping.isRoot) { |
| 1410 | 0 | query._conditions[schema.discriminatorMapping.key] = schema.discriminatorMapping.value; |
| 1411 | } | |
| 1412 | } | |
| 1413 | ||
| 1414 | /** | |
| 1415 | * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) update command. | |
| 1416 | * | |
| 1417 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed. | |
| 1418 | * | |
| 1419 | * ####Available options | |
| 1420 | * | |
| 1421 | * - `new`: bool - true to return the modified document rather than the original. defaults to true | |
| 1422 | * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. | |
| 1423 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1424 | * | |
| 1425 | * ####Examples | |
| 1426 | * | |
| 1427 | * query.findOneAndUpdate(conditions, update, options, callback) // executes | |
| 1428 | * query.findOneAndUpdate(conditions, update, options) // returns Query | |
| 1429 | * query.findOneAndUpdate(conditions, update, callback) // executes | |
| 1430 | * query.findOneAndUpdate(conditions, update) // returns Query | |
| 1431 | * query.findOneAndUpdate(update, callback) // returns Query | |
| 1432 | * query.findOneAndUpdate(update) // returns Query | |
| 1433 | * query.findOneAndUpdate(callback) // executes | |
| 1434 | * query.findOneAndUpdate() // returns Query | |
| 1435 | * | |
| 1436 | * @method findOneAndUpdate | |
| 1437 | * @memberOf Query | |
| 1438 | * @param {Object|Query} [query] | |
| 1439 | * @param {Object} [doc] | |
| 1440 | * @param {Object} [options] | |
| 1441 | * @param {Function} [callback] | |
| 1442 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1443 | * @return {Query} this | |
| 1444 | * @api public | |
| 1445 | */ | |
| 1446 | ||
| 1447 | /** | |
| 1448 | * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command. | |
| 1449 | * | |
| 1450 | * Finds a matching document, removes it, passing the found document (if any) to the callback. Executes immediately if `callback` is passed. | |
| 1451 | * | |
| 1452 | * ####Available options | |
| 1453 | * | |
| 1454 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1455 | * | |
| 1456 | * ####Examples | |
| 1457 | * | |
| 1458 | * A.where().findOneAndRemove(conditions, options, callback) // executes | |
| 1459 | * A.where().findOneAndRemove(conditions, options) // return Query | |
| 1460 | * A.where().findOneAndRemove(conditions, callback) // executes | |
| 1461 | * A.where().findOneAndRemove(conditions) // returns Query | |
| 1462 | * A.where().findOneAndRemove(callback) // executes | |
| 1463 | * A.where().findOneAndRemove() // returns Query | |
| 1464 | * | |
| 1465 | * @method findOneAndRemove | |
| 1466 | * @memberOf Query | |
| 1467 | * @param {Object} [conditions] | |
| 1468 | * @param {Object} [options] | |
| 1469 | * @param {Function} [callback] | |
| 1470 | * @return {Query} this | |
| 1471 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 1472 | * @api public | |
| 1473 | */ | |
| 1474 | ||
| 1475 | /** | |
| 1476 | * Override mquery.prototype._findAndModify to provide casting etc. | |
| 1477 | * | |
| 1478 | * @param {String} type - either "remove" or "update" | |
| 1479 | * @param {Function} callback | |
| 1480 | * @api private | |
| 1481 | */ | |
| 1482 | ||
| 1483 | 1 | Query.prototype._findAndModify = function (type, callback) { |
| 1484 | 0 | if ('function' != typeof callback) { |
| 1485 | 0 | throw new Error("Expected callback in _findAndModify"); |
| 1486 | } | |
| 1487 | ||
| 1488 | 0 | var model = this.model |
| 1489 | , promise = new Promise(callback) | |
| 1490 | , self = this | |
| 1491 | , castedQuery | |
| 1492 | , castedDoc | |
| 1493 | , fields | |
| 1494 | , opts; | |
| 1495 | ||
| 1496 | 0 | castedQuery = castQuery(this); |
| 1497 | 0 | if (castedQuery instanceof Error) { |
| 1498 | 0 | process.nextTick(promise.error.bind(promise, castedQuery)); |
| 1499 | 0 | return promise; |
| 1500 | } | |
| 1501 | ||
| 1502 | 0 | opts = this._optionsForExec(model); |
| 1503 | ||
| 1504 | 0 | if ('remove' == type) { |
| 1505 | 0 | opts.remove = true; |
| 1506 | } else { | |
| 1507 | 0 | if (!('new' in opts)) opts.new = true; |
| 1508 | 0 | if (!('upsert' in opts)) opts.upsert = false; |
| 1509 | ||
| 1510 | 0 | castedDoc = castDoc(this, opts.overwrite); |
| 1511 | 0 | if (!castedDoc) { |
| 1512 | 0 | if (opts.upsert) { |
| 1513 | // still need to do the upsert to empty doc | |
| 1514 | 0 | castedDoc = { $set: {} }; |
| 1515 | } else { | |
| 1516 | 0 | return this.findOne(callback); |
| 1517 | } | |
| 1518 | 0 | } else if (castedDoc instanceof Error) { |
| 1519 | 0 | process.nextTick(promise.error.bind(promise, castedDoc)); |
| 1520 | 0 | return promise; |
| 1521 | } | |
| 1522 | } | |
| 1523 | ||
| 1524 | 0 | this._applyPaths(); |
| 1525 | ||
| 1526 | 0 | var self = this; |
| 1527 | 0 | var options = this._mongooseOptions; |
| 1528 | ||
| 1529 | 0 | if (this._fields) { |
| 1530 | 0 | fields = utils.clone(this._fields); |
| 1531 | 0 | opts.fields = this._castFields(fields); |
| 1532 | 0 | if (opts.fields instanceof Error) { |
| 1533 | 0 | process.nextTick(promise.error.bind(promise, opts.fields)); |
| 1534 | 0 | return promise; |
| 1535 | } | |
| 1536 | } | |
| 1537 | ||
| 1538 | 0 | if (opts.sort) convertSortToArray(opts); |
| 1539 | ||
| 1540 | 0 | this._collection.findAndModify(castedQuery, castedDoc, opts, utils.tick(cb)); |
| 1541 | 0 | function cb (err, doc) { |
| 1542 | 0 | if (err) return promise.error(err); |
| 1543 | ||
| 1544 | 0 | if (!doc || (utils.isObject(doc) && Object.keys(doc).length === 0)) { |
| 1545 | 0 | return promise.complete(null); |
| 1546 | } | |
| 1547 | ||
| 1548 | 0 | if (!options.populate) { |
| 1549 | 0 | return true === options.lean |
| 1550 | ? promise.complete(doc) | |
| 1551 | : completeOne(self.model, doc, fields, self, null, promise); | |
| 1552 | } | |
| 1553 | ||
| 1554 | 0 | var pop = helpers.preparePopulationOptionsMQ(self, options); |
| 1555 | 0 | self.model.populate(doc, pop, function (err, doc) { |
| 1556 | 0 | if (err) return promise.error(err); |
| 1557 | ||
| 1558 | 0 | return true === options.lean |
| 1559 | ? promise.complete(doc) | |
| 1560 | : completeOne(self.model, doc, fields, self, pop, promise); | |
| 1561 | }); | |
| 1562 | } | |
| 1563 | ||
| 1564 | 0 | return promise; |
| 1565 | } | |
| 1566 | ||
| 1567 | /*! | |
| 1568 | * The mongodb driver 1.3.23 only supports the nested array sort | |
| 1569 | * syntax. We must convert it or sorting findAndModify will not work. | |
| 1570 | */ | |
| 1571 | ||
| 1572 | 1 | function convertSortToArray (opts) { |
| 1573 | 0 | if (Array.isArray(opts.sort)) return; |
| 1574 | 0 | if (!utils.isObject(opts.sort)) return; |
| 1575 | ||
| 1576 | 0 | var sort = []; |
| 1577 | ||
| 1578 | 0 | for (var key in opts.sort) if (utils.object.hasOwnProperty(opts.sort, key)) { |
| 1579 | 0 | sort.push([ key, opts.sort[key] ]); |
| 1580 | } | |
| 1581 | ||
| 1582 | 0 | opts.sort = sort; |
| 1583 | } | |
| 1584 | ||
| 1585 | /** | |
| 1586 | * Declare and/or execute this query as an update() operation. | |
| 1587 | * | |
| 1588 | * _All paths passed that are not $atomic operations will become $set ops._ | |
| 1589 | * | |
| 1590 | * ####Example | |
| 1591 | * | |
| 1592 | * Model.where({ _id: id }).update({ title: 'words' }) | |
| 1593 | * | |
| 1594 | * // becomes | |
| 1595 | * | |
| 1596 | * Model.where({ _id: id }).update({ $set: { title: 'words' }}) | |
| 1597 | * | |
| 1598 | * ####Note | |
| 1599 | * | |
| 1600 | * Passing an empty object `{}` as the doc will result in a no-op unless the `overwrite` option is passed. Without the `overwrite` option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection. | |
| 1601 | * | |
| 1602 | * ####Note | |
| 1603 | * | |
| 1604 | * The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call update() and then execute it by using the `exec()` method. | |
| 1605 | * | |
| 1606 | * var q = Model.where({ _id: id }); | |
| 1607 | * q.update({ $set: { name: 'bob' }}).update(); // not executed | |
| 1608 | * | |
| 1609 | * q.update({ $set: { name: 'bob' }}).exec(); // executed as unsafe | |
| 1610 | * | |
| 1611 | * // keys that are not $atomic ops become $set. | |
| 1612 | * // this executes the same command as the previous example. | |
| 1613 | * q.update({ name: 'bob' }).exec(); | |
| 1614 | * | |
| 1615 | * // overwriting with empty docs | |
| 1616 | * var q = Model.where({ _id: id }).setOptions({ overwrite: true }) | |
| 1617 | * q.update({ }, callback); // executes | |
| 1618 | * | |
| 1619 | * // multi update with overwrite to empty doc | |
| 1620 | * var q = Model.where({ _id: id }); | |
| 1621 | * q.setOptions({ multi: true, overwrite: true }) | |
| 1622 | * q.update({ }); | |
| 1623 | * q.update(callback); // executed | |
| 1624 | * | |
| 1625 | * // multi updates | |
| 1626 | * Model.where() | |
| 1627 | * .update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback) | |
| 1628 | * | |
| 1629 | * // more multi updates | |
| 1630 | * Model.where() | |
| 1631 | * .setOptions({ multi: true }) | |
| 1632 | * .update({ $set: { arr: [] }}, callback) | |
| 1633 | * | |
| 1634 | * // single update by default | |
| 1635 | * Model.where({ email: 'address@example.com' }) | |
| 1636 | * .update({ $inc: { counter: 1 }}, callback) | |
| 1637 | * | |
| 1638 | * API summary | |
| 1639 | * | |
| 1640 | * update(criteria, doc, options, cb) // executes | |
| 1641 | * update(criteria, doc, options) | |
| 1642 | * update(criteria, doc, cb) // executes | |
| 1643 | * update(criteria, doc) | |
| 1644 | * update(doc, cb) // executes | |
| 1645 | * update(doc) | |
| 1646 | * update(cb) // executes | |
| 1647 | * update(true) // executes (unsafe write) | |
| 1648 | * update() | |
| 1649 | * | |
| 1650 | * @param {Object} [criteria] | |
| 1651 | * @param {Object} [doc] the update command | |
| 1652 | * @param {Object} [options] | |
| 1653 | * @param {Function} [callback] | |
| 1654 | * @return {Query} this | |
| 1655 | * @see Model.update #model_Model.update | |
| 1656 | * @see update http://docs.mongodb.org/manual/reference/method/db.collection.update/ | |
| 1657 | * @api public | |
| 1658 | */ | |
| 1659 | ||
| 1660 | 1 | Query.prototype.update = function (conditions, doc, options, callback) { |
| 1661 | 0 | if ('function' === typeof options) { |
| 1662 | // Scenario: update(conditions, doc, callback) | |
| 1663 | 0 | callback = options; |
| 1664 | 0 | options = null; |
| 1665 | 0 | } else if ('function' === typeof doc) { |
| 1666 | // Scenario: update(doc, callback); | |
| 1667 | 0 | callback = doc; |
| 1668 | 0 | doc = conditions; |
| 1669 | 0 | conditions = {}; |
| 1670 | 0 | options = null; |
| 1671 | 0 | } else if ('function' === typeof conditions) { |
| 1672 | 0 | callback = conditions; |
| 1673 | 0 | conditions = undefined; |
| 1674 | 0 | doc = undefined; |
| 1675 | 0 | options = undefined; |
| 1676 | } | |
| 1677 | ||
| 1678 | // make sure we don't send in the whole Document to merge() | |
| 1679 | 0 | if (conditions instanceof Document) { |
| 1680 | 0 | conditions = conditions.toObject(); |
| 1681 | } | |
| 1682 | ||
| 1683 | // strict is an option used in the update checking, make sure it gets set | |
| 1684 | 0 | if (options) { |
| 1685 | 0 | if ('strict' in options) { |
| 1686 | 0 | this._mongooseOptions.strict = options.strict; |
| 1687 | } | |
| 1688 | } | |
| 1689 | ||
| 1690 | // if doc is undefined at this point, this means this function is being | |
| 1691 | // executed by exec(not always see below). Grab the update doc from here in | |
| 1692 | // order to validate | |
| 1693 | // This could also be somebody calling update() or update({}). Probably not a | |
| 1694 | // common use case, check for _update to make sure we don't do anything bad | |
| 1695 | 0 | if (!doc && this._update) { |
| 1696 | 0 | doc = this._updateForExec(); |
| 1697 | } | |
| 1698 | ||
| 1699 | 0 | if (conditions) { |
| 1700 | 0 | this._conditions = conditions; |
| 1701 | } | |
| 1702 | ||
| 1703 | // validate the selector part of the query | |
| 1704 | 0 | var castedQuery = castQuery(this); |
| 1705 | 0 | if (castedQuery instanceof Error) { |
| 1706 | 0 | if(callback) { |
| 1707 | 0 | callback(castedQuery); |
| 1708 | 0 | return this; |
| 1709 | } else { | |
| 1710 | 0 | throw castedQuery; |
| 1711 | } | |
| 1712 | } | |
| 1713 | ||
| 1714 | // validate the update part of the query | |
| 1715 | 0 | var castedDoc; |
| 1716 | 0 | try { |
| 1717 | 0 | castedDoc = this._castUpdate(doc, options && options.overwrite); |
| 1718 | } catch (err) { | |
| 1719 | 0 | if (callback) { |
| 1720 | 0 | callback(err); |
| 1721 | 0 | return this; |
| 1722 | } else { | |
| 1723 | 0 | throw err; |
| 1724 | } | |
| 1725 | } | |
| 1726 | ||
| 1727 | 0 | if (!castedDoc) { |
| 1728 | 0 | callback && callback(null, 0); |
| 1729 | 0 | return this; |
| 1730 | } | |
| 1731 | ||
| 1732 | 0 | return Query.base.update.call(this, castedQuery, castedDoc, options, callback); |
| 1733 | } | |
| 1734 | ||
| 1735 | /** | |
| 1736 | * Executes the query | |
| 1737 | * | |
| 1738 | * ####Examples: | |
| 1739 | * | |
| 1740 | * var promise = query.exec(); | |
| 1741 | * var promise = query.exec('update'); | |
| 1742 | * | |
| 1743 | * query.exec(callback); | |
| 1744 | * query.exec('find', callback); | |
| 1745 | * | |
| 1746 | * @param {String|Function} [operation] | |
| 1747 | * @param {Function} [callback] | |
| 1748 | * @return {Promise} | |
| 1749 | * @api public | |
| 1750 | */ | |
| 1751 | ||
| 1752 | 1 | Query.prototype.exec = function exec (op, callback) { |
| 1753 | 0 | var promise = new Promise(); |
| 1754 | ||
| 1755 | 0 | if ('function' == typeof op) { |
| 1756 | 0 | callback = op; |
| 1757 | 0 | op = null; |
| 1758 | 0 | } else if ('string' == typeof op) { |
| 1759 | 0 | this.op = op; |
| 1760 | } | |
| 1761 | ||
| 1762 | 0 | if (callback) promise.addBack(callback); |
| 1763 | ||
| 1764 | 0 | if (!this.op) { |
| 1765 | 0 | promise.complete(); |
| 1766 | 0 | return promise; |
| 1767 | } | |
| 1768 | ||
| 1769 | 0 | Query.base.exec.call(this, op, promise.resolve.bind(promise)); |
| 1770 | ||
| 1771 | 0 | return promise; |
| 1772 | } | |
| 1773 | ||
| 1774 | /** | |
| 1775 | * Finds the schema for `path`. This is different than | |
| 1776 | * calling `schema.path` as it also resolves paths with | |
| 1777 | * positional selectors (something.$.another.$.path). | |
| 1778 | * | |
| 1779 | * @param {String} path | |
| 1780 | * @api private | |
| 1781 | */ | |
| 1782 | ||
| 1783 | 1 | Query.prototype._getSchema = function _getSchema (path) { |
| 1784 | 0 | return this.model._getSchema(path); |
| 1785 | } | |
| 1786 | ||
| 1787 | /*! | |
| 1788 | * These operators require casting docs | |
| 1789 | * to real Documents for Update operations. | |
| 1790 | */ | |
| 1791 | ||
| 1792 | 1 | var castOps = { |
| 1793 | $push: 1 | |
| 1794 | , $pushAll: 1 | |
| 1795 | , $addToSet: 1 | |
| 1796 | , $set: 1 | |
| 1797 | }; | |
| 1798 | ||
| 1799 | /*! | |
| 1800 | * These operators should be cast to numbers instead | |
| 1801 | * of their path schema type. | |
| 1802 | */ | |
| 1803 | ||
| 1804 | 1 | var numberOps = { |
| 1805 | $pop: 1 | |
| 1806 | , $unset: 1 | |
| 1807 | , $inc: 1 | |
| 1808 | } | |
| 1809 | ||
| 1810 | /** | |
| 1811 | * Casts obj for an update command. | |
| 1812 | * | |
| 1813 | * @param {Object} obj | |
| 1814 | * @return {Object} obj after casting its values | |
| 1815 | * @api private | |
| 1816 | */ | |
| 1817 | ||
| 1818 | 1 | Query.prototype._castUpdate = function _castUpdate (obj, overwrite) { |
| 1819 | 0 | if (!obj) return undefined; |
| 1820 | ||
| 1821 | 0 | var ops = Object.keys(obj) |
| 1822 | , i = ops.length | |
| 1823 | , ret = {} | |
| 1824 | , hasKeys | |
| 1825 | , val | |
| 1826 | ||
| 1827 | 0 | while (i--) { |
| 1828 | 0 | var op = ops[i]; |
| 1829 | // if overwrite is set, don't do any of the special $set stuff | |
| 1830 | 0 | if ('$' !== op[0] && !overwrite) { |
| 1831 | // fix up $set sugar | |
| 1832 | 0 | if (!ret.$set) { |
| 1833 | 0 | if (obj.$set) { |
| 1834 | 0 | ret.$set = obj.$set; |
| 1835 | } else { | |
| 1836 | 0 | ret.$set = {}; |
| 1837 | } | |
| 1838 | } | |
| 1839 | 0 | ret.$set[op] = obj[op]; |
| 1840 | 0 | ops.splice(i, 1); |
| 1841 | 0 | if (!~ops.indexOf('$set')) ops.push('$set'); |
| 1842 | 0 | } else if ('$set' === op) { |
| 1843 | 0 | if (!ret.$set) { |
| 1844 | 0 | ret[op] = obj[op]; |
| 1845 | } | |
| 1846 | } else { | |
| 1847 | 0 | ret[op] = obj[op]; |
| 1848 | } | |
| 1849 | } | |
| 1850 | ||
| 1851 | // cast each value | |
| 1852 | 0 | i = ops.length; |
| 1853 | ||
| 1854 | // if we get passed {} for the update, we still need to respect that when it | |
| 1855 | // is an overwrite scenario | |
| 1856 | 0 | if (overwrite) { |
| 1857 | 0 | hasKeys = true; |
| 1858 | } | |
| 1859 | ||
| 1860 | 0 | while (i--) { |
| 1861 | 0 | op = ops[i]; |
| 1862 | 0 | val = ret[op]; |
| 1863 | 0 | if ('Object' === val.constructor.name && !overwrite) { |
| 1864 | 0 | hasKeys |= this._walkUpdatePath(val, op); |
| 1865 | 0 | } else if (overwrite && 'Object' === ret.constructor.name) { |
| 1866 | // if we are just using overwrite, cast the query and then we will | |
| 1867 | // *always* return the value, even if it is an empty object. We need to | |
| 1868 | // set hasKeys above because we need to account for the case where the | |
| 1869 | // user passes {} and wants to clobber the whole document | |
| 1870 | // Also, _walkUpdatePath expects an operation, so give it $set since that | |
| 1871 | // is basically what we're doing | |
| 1872 | 0 | this._walkUpdatePath(ret, '$set'); |
| 1873 | } else { | |
| 1874 | 0 | var msg = 'Invalid atomic update value for ' + op + '. ' |
| 1875 | + 'Expected an object, received ' + typeof val; | |
| 1876 | 0 | throw new Error(msg); |
| 1877 | } | |
| 1878 | } | |
| 1879 | ||
| 1880 | 0 | return hasKeys && ret; |
| 1881 | } | |
| 1882 | ||
| 1883 | /** | |
| 1884 | * Walk each path of obj and cast its values | |
| 1885 | * according to its schema. | |
| 1886 | * | |
| 1887 | * @param {Object} obj - part of a query | |
| 1888 | * @param {String} op - the atomic operator ($pull, $set, etc) | |
| 1889 | * @param {String} pref - path prefix (internal only) | |
| 1890 | * @return {Bool} true if this path has keys to update | |
| 1891 | * @api private | |
| 1892 | */ | |
| 1893 | ||
| 1894 | 1 | Query.prototype._walkUpdatePath = function _walkUpdatePath (obj, op, pref) { |
| 1895 | 0 | var prefix = pref ? pref + '.' : '' |
| 1896 | , keys = Object.keys(obj) | |
| 1897 | , i = keys.length | |
| 1898 | , hasKeys = false | |
| 1899 | , schema | |
| 1900 | , key | |
| 1901 | , val | |
| 1902 | ||
| 1903 | 0 | var strict = 'strict' in this._mongooseOptions |
| 1904 | ? this._mongooseOptions.strict | |
| 1905 | : this.model.schema.options.strict; | |
| 1906 | ||
| 1907 | 0 | while (i--) { |
| 1908 | 0 | key = keys[i]; |
| 1909 | 0 | val = obj[key]; |
| 1910 | ||
| 1911 | 0 | if (val && 'Object' === val.constructor.name) { |
| 1912 | // watch for embedded doc schemas | |
| 1913 | 0 | schema = this._getSchema(prefix + key); |
| 1914 | 0 | if (schema && schema.caster && op in castOps) { |
| 1915 | // embedded doc schema | |
| 1916 | ||
| 1917 | 0 | if (strict && !schema) { |
| 1918 | // path is not in our strict schema | |
| 1919 | 0 | if ('throw' == strict) { |
| 1920 | 0 | throw new Error('Field `' + key + '` is not in schema.'); |
| 1921 | } else { | |
| 1922 | // ignore paths not specified in schema | |
| 1923 | 0 | delete obj[key]; |
| 1924 | } | |
| 1925 | } else { | |
| 1926 | 0 | hasKeys = true; |
| 1927 | ||
| 1928 | 0 | if ('$each' in val) { |
| 1929 | 0 | obj[key] = { |
| 1930 | $each: this._castUpdateVal(schema, val.$each, op) | |
| 1931 | } | |
| 1932 | ||
| 1933 | 0 | if (val.$slice) { |
| 1934 | 0 | obj[key].$slice = val.$slice | 0; |
| 1935 | } | |
| 1936 | ||
| 1937 | 0 | if (val.$sort) { |
| 1938 | 0 | obj[key].$sort = val.$sort; |
| 1939 | } | |
| 1940 | ||
| 1941 | } else { | |
| 1942 | 0 | obj[key] = this._castUpdateVal(schema, val, op); |
| 1943 | } | |
| 1944 | } | |
| 1945 | } else { | |
| 1946 | 0 | hasKeys |= this._walkUpdatePath(val, op, prefix + key); |
| 1947 | } | |
| 1948 | } else { | |
| 1949 | 0 | schema = '$each' === key |
| 1950 | ? this._getSchema(pref) | |
| 1951 | : this._getSchema(prefix + key); | |
| 1952 | ||
| 1953 | 0 | var skip = strict && |
| 1954 | !schema && | |
| 1955 | !/real|nested/.test(this.model.schema.pathType(prefix + key)); | |
| 1956 | ||
| 1957 | 0 | if (skip) { |
| 1958 | 0 | if ('throw' == strict) { |
| 1959 | 0 | throw new Error('Field `' + prefix + key + '` is not in schema.'); |
| 1960 | } else { | |
| 1961 | 0 | delete obj[key]; |
| 1962 | } | |
| 1963 | } else { | |
| 1964 | 0 | hasKeys = true; |
| 1965 | 0 | obj[key] = this._castUpdateVal(schema, val, op, key); |
| 1966 | } | |
| 1967 | } | |
| 1968 | } | |
| 1969 | 0 | return hasKeys; |
| 1970 | } | |
| 1971 | ||
| 1972 | /** | |
| 1973 | * Casts `val` according to `schema` and atomic `op`. | |
| 1974 | * | |
| 1975 | * @param {Schema} schema | |
| 1976 | * @param {Object} val | |
| 1977 | * @param {String} op - the atomic operator ($pull, $set, etc) | |
| 1978 | * @param {String} [$conditional] | |
| 1979 | * @api private | |
| 1980 | */ | |
| 1981 | ||
| 1982 | 1 | Query.prototype._castUpdateVal = function _castUpdateVal (schema, val, op, $conditional) { |
| 1983 | 0 | if (!schema) { |
| 1984 | // non-existing schema path | |
| 1985 | 0 | return op in numberOps |
| 1986 | ? Number(val) | |
| 1987 | : val | |
| 1988 | } | |
| 1989 | ||
| 1990 | 0 | if (schema.caster && op in castOps && |
| 1991 | (utils.isObject(val) || Array.isArray(val))) { | |
| 1992 | // Cast values for ops that add data to MongoDB. | |
| 1993 | // Ensures embedded documents get ObjectIds etc. | |
| 1994 | 0 | var tmp = schema.cast(val); |
| 1995 | ||
| 1996 | 0 | if (Array.isArray(val)) { |
| 1997 | 0 | val = tmp; |
| 1998 | } else { | |
| 1999 | 0 | val = tmp[0]; |
| 2000 | } | |
| 2001 | } | |
| 2002 | ||
| 2003 | 0 | if (op in numberOps) return Number(val); |
| 2004 | 0 | if (/^\$/.test($conditional)) return schema.castForQuery($conditional, val); |
| 2005 | 0 | return schema.castForQuery(val) |
| 2006 | } | |
| 2007 | ||
| 2008 | /*! | |
| 2009 | * castQuery | |
| 2010 | * @api private | |
| 2011 | */ | |
| 2012 | ||
| 2013 | 1 | function castQuery (query) { |
| 2014 | 0 | try { |
| 2015 | 0 | return query.cast(query.model); |
| 2016 | } catch (err) { | |
| 2017 | 0 | return err; |
| 2018 | } | |
| 2019 | } | |
| 2020 | ||
| 2021 | /*! | |
| 2022 | * castDoc | |
| 2023 | * @api private | |
| 2024 | */ | |
| 2025 | ||
| 2026 | 1 | function castDoc (query, overwrite) { |
| 2027 | 0 | try { |
| 2028 | 0 | return query._castUpdate(query._update, overwrite); |
| 2029 | } catch (err) { | |
| 2030 | 0 | return err; |
| 2031 | } | |
| 2032 | } | |
| 2033 | ||
| 2034 | /** | |
| 2035 | * Specifies paths which should be populated with other documents. | |
| 2036 | * | |
| 2037 | * ####Example: | |
| 2038 | * | |
| 2039 | * Kitten.findOne().populate('owner').exec(function (err, kitten) { | |
| 2040 | * console.log(kitten.owner.name) // Max | |
| 2041 | * }) | |
| 2042 | * | |
| 2043 | * Kitten.find().populate({ | |
| 2044 | * path: 'owner' | |
| 2045 | * , select: 'name' | |
| 2046 | * , match: { color: 'black' } | |
| 2047 | * , options: { sort: { name: -1 }} | |
| 2048 | * }).exec(function (err, kittens) { | |
| 2049 | * console.log(kittens[0].owner.name) // Zoopa | |
| 2050 | * }) | |
| 2051 | * | |
| 2052 | * // alternatively | |
| 2053 | * Kitten.find().populate('owner', 'name', null, {sort: { name: -1 }}).exec(function (err, kittens) { | |
| 2054 | * console.log(kittens[0].owner.name) // Zoopa | |
| 2055 | * }) | |
| 2056 | * | |
| 2057 | * Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback. | |
| 2058 | * | |
| 2059 | * @param {Object|String} path either the path to populate or an object specifying all parameters | |
| 2060 | * @param {Object|String} [select] Field selection for the population query | |
| 2061 | * @param {Model} [model] The name of the model you wish to use for population. If not specified, the name is looked up from the Schema ref. | |
| 2062 | * @param {Object} [match] Conditions for the population query | |
| 2063 | * @param {Object} [options] Options for the population query (sort, etc) | |
| 2064 | * @see population ./populate.html | |
| 2065 | * @see Query#select #query_Query-select | |
| 2066 | * @see Model.populate #model_Model.populate | |
| 2067 | * @return {Query} this | |
| 2068 | * @api public | |
| 2069 | */ | |
| 2070 | ||
| 2071 | 1 | Query.prototype.populate = function () { |
| 2072 | 0 | var res = utils.populate.apply(null, arguments); |
| 2073 | 0 | var opts = this._mongooseOptions; |
| 2074 | ||
| 2075 | 0 | if (!utils.isObject(opts.populate)) { |
| 2076 | 0 | opts.populate = {}; |
| 2077 | } | |
| 2078 | ||
| 2079 | 0 | for (var i = 0; i < res.length; ++i) { |
| 2080 | 0 | opts.populate[res[i].path] = res[i]; |
| 2081 | } | |
| 2082 | ||
| 2083 | 0 | return this; |
| 2084 | } | |
| 2085 | ||
| 2086 | /** | |
| 2087 | * Casts this query to the schema of `model` | |
| 2088 | * | |
| 2089 | * ####Note | |
| 2090 | * | |
| 2091 | * If `obj` is present, it is cast instead of this query. | |
| 2092 | * | |
| 2093 | * @param {Model} model | |
| 2094 | * @param {Object} [obj] | |
| 2095 | * @return {Object} | |
| 2096 | * @api public | |
| 2097 | */ | |
| 2098 | ||
| 2099 | 1 | Query.prototype.cast = function (model, obj) { |
| 2100 | 0 | obj || (obj = this._conditions); |
| 2101 | ||
| 2102 | 0 | var schema = model.schema |
| 2103 | , paths = Object.keys(obj) | |
| 2104 | , i = paths.length | |
| 2105 | , any$conditionals | |
| 2106 | , schematype | |
| 2107 | , nested | |
| 2108 | , path | |
| 2109 | , type | |
| 2110 | , val; | |
| 2111 | ||
| 2112 | 0 | while (i--) { |
| 2113 | 0 | path = paths[i]; |
| 2114 | 0 | val = obj[path]; |
| 2115 | ||
| 2116 | 0 | if ('$or' === path || '$nor' === path || '$and' === path) { |
| 2117 | 0 | var k = val.length |
| 2118 | , orComponentQuery; | |
| 2119 | ||
| 2120 | 0 | while (k--) { |
| 2121 | 0 | orComponentQuery = new Query(val[k], {}, null, this.mongooseCollection); |
| 2122 | 0 | orComponentQuery.cast(model); |
| 2123 | 0 | val[k] = orComponentQuery._conditions; |
| 2124 | } | |
| 2125 | ||
| 2126 | 0 | } else if (path === '$where') { |
| 2127 | 0 | type = typeof val; |
| 2128 | ||
| 2129 | 0 | if ('string' !== type && 'function' !== type) { |
| 2130 | 0 | throw new Error("Must have a string or function for $where"); |
| 2131 | } | |
| 2132 | ||
| 2133 | 0 | if ('function' === type) { |
| 2134 | 0 | obj[path] = val.toString(); |
| 2135 | } | |
| 2136 | ||
| 2137 | 0 | continue; |
| 2138 | ||
| 2139 | } else { | |
| 2140 | ||
| 2141 | 0 | if (!schema) { |
| 2142 | // no casting for Mixed types | |
| 2143 | 0 | continue; |
| 2144 | } | |
| 2145 | ||
| 2146 | 0 | schematype = schema.path(path); |
| 2147 | ||
| 2148 | 0 | if (!schematype) { |
| 2149 | // Handle potential embedded array queries | |
| 2150 | 0 | var split = path.split('.') |
| 2151 | , j = split.length | |
| 2152 | , pathFirstHalf | |
| 2153 | , pathLastHalf | |
| 2154 | , remainingConds | |
| 2155 | , castingQuery; | |
| 2156 | ||
| 2157 | // Find the part of the var path that is a path of the Schema | |
| 2158 | 0 | while (j--) { |
| 2159 | 0 | pathFirstHalf = split.slice(0, j).join('.'); |
| 2160 | 0 | schematype = schema.path(pathFirstHalf); |
| 2161 | 0 | if (schematype) break; |
| 2162 | } | |
| 2163 | ||
| 2164 | // If a substring of the input path resolves to an actual real path... | |
| 2165 | 0 | if (schematype) { |
| 2166 | // Apply the casting; similar code for $elemMatch in schema/array.js | |
| 2167 | 0 | if (schematype.caster && schematype.caster.schema) { |
| 2168 | 0 | remainingConds = {}; |
| 2169 | 0 | pathLastHalf = split.slice(j).join('.'); |
| 2170 | 0 | remainingConds[pathLastHalf] = val; |
| 2171 | 0 | castingQuery = new Query(remainingConds, {}, null, this.mongooseCollection); |
| 2172 | 0 | castingQuery.cast(schematype.caster); |
| 2173 | 0 | obj[path] = castingQuery._conditions[pathLastHalf]; |
| 2174 | } else { | |
| 2175 | 0 | obj[path] = val; |
| 2176 | } | |
| 2177 | 0 | continue; |
| 2178 | } | |
| 2179 | ||
| 2180 | 0 | if (utils.isObject(val)) { |
| 2181 | // handle geo schemas that use object notation | |
| 2182 | // { loc: { long: Number, lat: Number } | |
| 2183 | ||
| 2184 | 0 | var geo = val.$near ? '$near' : |
| 2185 | val.$nearSphere ? '$nearSphere' : | |
| 2186 | val.$within ? '$within' : | |
| 2187 | val.$geoIntersects ? '$geoIntersects' : ''; | |
| 2188 | ||
| 2189 | 0 | if (!geo) { |
| 2190 | 0 | continue; |
| 2191 | } | |
| 2192 | ||
| 2193 | 0 | var numbertype = new Types.Number('__QueryCasting__') |
| 2194 | 0 | var value = val[geo]; |
| 2195 | ||
| 2196 | 0 | if (val.$maxDistance) { |
| 2197 | 0 | val.$maxDistance = numbertype.castForQuery(val.$maxDistance); |
| 2198 | } | |
| 2199 | ||
| 2200 | 0 | if ('$within' == geo) { |
| 2201 | 0 | var withinType = value.$center |
| 2202 | || value.$centerSphere | |
| 2203 | || value.$box | |
| 2204 | || value.$polygon; | |
| 2205 | ||
| 2206 | 0 | if (!withinType) { |
| 2207 | 0 | throw new Error('Bad $within paramater: ' + JSON.stringify(val)); |
| 2208 | } | |
| 2209 | ||
| 2210 | 0 | value = withinType; |
| 2211 | ||
| 2212 | 0 | } else if ('$near' == geo && |
| 2213 | 'string' == typeof value.type && Array.isArray(value.coordinates)) { | |
| 2214 | // geojson; cast the coordinates | |
| 2215 | 0 | value = value.coordinates; |
| 2216 | ||
| 2217 | 0 | } else if (('$near' == geo || '$geoIntersects' == geo) && |
| 2218 | value.$geometry && 'string' == typeof value.$geometry.type && | |
| 2219 | Array.isArray(value.$geometry.coordinates)) { | |
| 2220 | // geojson; cast the coordinates | |
| 2221 | 0 | value = value.$geometry.coordinates; |
| 2222 | } | |
| 2223 | ||
| 2224 | 0 | ;(function _cast (val) { |
| 2225 | 0 | if (Array.isArray(val)) { |
| 2226 | 0 | val.forEach(function (item, i) { |
| 2227 | 0 | if (Array.isArray(item) || utils.isObject(item)) { |
| 2228 | 0 | return _cast(item); |
| 2229 | } | |
| 2230 | 0 | val[i] = numbertype.castForQuery(item); |
| 2231 | }); | |
| 2232 | } else { | |
| 2233 | 0 | var nearKeys= Object.keys(val); |
| 2234 | 0 | var nearLen = nearKeys.length; |
| 2235 | 0 | while (nearLen--) { |
| 2236 | 0 | var nkey = nearKeys[nearLen]; |
| 2237 | 0 | var item = val[nkey]; |
| 2238 | 0 | if (Array.isArray(item) || utils.isObject(item)) { |
| 2239 | 0 | _cast(item); |
| 2240 | 0 | val[nkey] = item; |
| 2241 | } else { | |
| 2242 | 0 | val[nkey] = numbertype.castForQuery(item); |
| 2243 | } | |
| 2244 | } | |
| 2245 | } | |
| 2246 | })(value); | |
| 2247 | } | |
| 2248 | ||
| 2249 | 0 | } else if (val === null || val === undefined) { |
| 2250 | 0 | continue; |
| 2251 | 0 | } else if ('Object' === val.constructor.name) { |
| 2252 | ||
| 2253 | 0 | any$conditionals = Object.keys(val).some(function (k) { |
| 2254 | 0 | return k.charAt(0) === '$' && k !== '$id' && k !== '$ref'; |
| 2255 | }); | |
| 2256 | ||
| 2257 | 0 | if (!any$conditionals) { |
| 2258 | 0 | obj[path] = schematype.castForQuery(val); |
| 2259 | } else { | |
| 2260 | ||
| 2261 | 0 | var ks = Object.keys(val) |
| 2262 | , k = ks.length | |
| 2263 | , $cond; | |
| 2264 | ||
| 2265 | 0 | while (k--) { |
| 2266 | 0 | $cond = ks[k]; |
| 2267 | 0 | nested = val[$cond]; |
| 2268 | ||
| 2269 | 0 | if ('$exists' === $cond) { |
| 2270 | 0 | if ('boolean' !== typeof nested) { |
| 2271 | 0 | throw new Error("$exists parameter must be Boolean"); |
| 2272 | } | |
| 2273 | 0 | continue; |
| 2274 | } | |
| 2275 | ||
| 2276 | 0 | if ('$type' === $cond) { |
| 2277 | 0 | if ('number' !== typeof nested) { |
| 2278 | 0 | throw new Error("$type parameter must be Number"); |
| 2279 | } | |
| 2280 | 0 | continue; |
| 2281 | } | |
| 2282 | ||
| 2283 | 0 | if ('$not' === $cond) { |
| 2284 | 0 | this.cast(model, nested); |
| 2285 | } else { | |
| 2286 | 0 | val[$cond] = schematype.castForQuery($cond, nested); |
| 2287 | } | |
| 2288 | } | |
| 2289 | } | |
| 2290 | } else { | |
| 2291 | 0 | obj[path] = schematype.castForQuery(val); |
| 2292 | } | |
| 2293 | } | |
| 2294 | } | |
| 2295 | ||
| 2296 | 0 | return obj; |
| 2297 | } | |
| 2298 | ||
| 2299 | /** | |
| 2300 | * Casts selected field arguments for field selection with mongo 2.2 | |
| 2301 | * | |
| 2302 | * query.select({ ids: { $elemMatch: { $in: [hexString] }}) | |
| 2303 | * | |
| 2304 | * @param {Object} fields | |
| 2305 | * @see https://github.com/LearnBoost/mongoose/issues/1091 | |
| 2306 | * @see http://docs.mongodb.org/manual/reference/projection/elemMatch/ | |
| 2307 | * @api private | |
| 2308 | */ | |
| 2309 | ||
| 2310 | 1 | Query.prototype._castFields = function _castFields (fields) { |
| 2311 | 0 | var selected |
| 2312 | , elemMatchKeys | |
| 2313 | , keys | |
| 2314 | , key | |
| 2315 | , out | |
| 2316 | , i | |
| 2317 | ||
| 2318 | 0 | if (fields) { |
| 2319 | 0 | keys = Object.keys(fields); |
| 2320 | 0 | elemMatchKeys = []; |
| 2321 | 0 | i = keys.length; |
| 2322 | ||
| 2323 | // collect $elemMatch args | |
| 2324 | 0 | while (i--) { |
| 2325 | 0 | key = keys[i]; |
| 2326 | 0 | if (fields[key].$elemMatch) { |
| 2327 | 0 | selected || (selected = {}); |
| 2328 | 0 | selected[key] = fields[key]; |
| 2329 | 0 | elemMatchKeys.push(key); |
| 2330 | } | |
| 2331 | } | |
| 2332 | } | |
| 2333 | ||
| 2334 | 0 | if (selected) { |
| 2335 | // they passed $elemMatch, cast em | |
| 2336 | 0 | try { |
| 2337 | 0 | out = this.cast(this.model, selected); |
| 2338 | } catch (err) { | |
| 2339 | 0 | return err; |
| 2340 | } | |
| 2341 | ||
| 2342 | // apply the casted field args | |
| 2343 | 0 | i = elemMatchKeys.length; |
| 2344 | 0 | while (i--) { |
| 2345 | 0 | key = elemMatchKeys[i]; |
| 2346 | 0 | fields[key] = out[key]; |
| 2347 | } | |
| 2348 | } | |
| 2349 | ||
| 2350 | 0 | return fields; |
| 2351 | } | |
| 2352 | ||
| 2353 | /** | |
| 2354 | * Applies schematype selected options to this query. | |
| 2355 | * @api private | |
| 2356 | */ | |
| 2357 | ||
| 2358 | 1 | Query.prototype._applyPaths = function applyPaths () { |
| 2359 | // determine if query is selecting or excluding fields | |
| 2360 | ||
| 2361 | 0 | var fields = this._fields |
| 2362 | , exclude | |
| 2363 | , keys | |
| 2364 | , ki | |
| 2365 | ||
| 2366 | 0 | if (fields) { |
| 2367 | 0 | keys = Object.keys(fields); |
| 2368 | 0 | ki = keys.length; |
| 2369 | ||
| 2370 | 0 | while (ki--) { |
| 2371 | 0 | if ('+' == keys[ki][0]) continue; |
| 2372 | 0 | exclude = 0 === fields[keys[ki]]; |
| 2373 | 0 | break; |
| 2374 | } | |
| 2375 | } | |
| 2376 | ||
| 2377 | // if selecting, apply default schematype select:true fields | |
| 2378 | // if excluding, apply schematype select:false fields | |
| 2379 | ||
| 2380 | 0 | var selected = [] |
| 2381 | , excluded = [] | |
| 2382 | , seen = []; | |
| 2383 | ||
| 2384 | 0 | analyzeSchema(this.model.schema); |
| 2385 | ||
| 2386 | 0 | switch (exclude) { |
| 2387 | case true: | |
| 2388 | 0 | excluded.length && this.select('-' + excluded.join(' -')); |
| 2389 | 0 | break; |
| 2390 | case false: | |
| 2391 | 0 | selected.length && this.select(selected.join(' ')); |
| 2392 | 0 | break; |
| 2393 | case undefined: | |
| 2394 | // user didn't specify fields, implies returning all fields. | |
| 2395 | // only need to apply excluded fields | |
| 2396 | 0 | excluded.length && this.select('-' + excluded.join(' -')); |
| 2397 | 0 | break; |
| 2398 | } | |
| 2399 | ||
| 2400 | 0 | return seen = excluded = selected = keys = fields = null; |
| 2401 | ||
| 2402 | 0 | function analyzeSchema (schema, prefix) { |
| 2403 | 0 | prefix || (prefix = ''); |
| 2404 | ||
| 2405 | // avoid recursion | |
| 2406 | 0 | if (~seen.indexOf(schema)) return; |
| 2407 | 0 | seen.push(schema); |
| 2408 | ||
| 2409 | 0 | schema.eachPath(function (path, type) { |
| 2410 | 0 | if (prefix) path = prefix + '.' + path; |
| 2411 | ||
| 2412 | 0 | analyzePath(path, type); |
| 2413 | ||
| 2414 | // array of subdocs? | |
| 2415 | 0 | if (type.schema) { |
| 2416 | 0 | analyzeSchema(type.schema, path); |
| 2417 | } | |
| 2418 | ||
| 2419 | }); | |
| 2420 | } | |
| 2421 | ||
| 2422 | 0 | function analyzePath (path, type) { |
| 2423 | 0 | if ('boolean' != typeof type.selected) return; |
| 2424 | ||
| 2425 | 0 | var plusPath = '+' + path; |
| 2426 | 0 | if (fields && plusPath in fields) { |
| 2427 | // forced inclusion | |
| 2428 | 0 | delete fields[plusPath]; |
| 2429 | ||
| 2430 | // if there are other fields being included, add this one | |
| 2431 | // if no other included fields, leave this out (implied inclusion) | |
| 2432 | 0 | if (false === exclude && keys.length > 1 && !~keys.indexOf(path)) { |
| 2433 | 0 | fields[path] = 1; |
| 2434 | } | |
| 2435 | ||
| 2436 | 0 | return |
| 2437 | }; | |
| 2438 | ||
| 2439 | // check for parent exclusions | |
| 2440 | 0 | var root = path.split('.')[0]; |
| 2441 | 0 | if (~excluded.indexOf(root)) return; |
| 2442 | ||
| 2443 | 0 | ;(type.selected ? selected : excluded).push(path); |
| 2444 | } | |
| 2445 | } | |
| 2446 | ||
| 2447 | /** | |
| 2448 | * Casts selected field arguments for field selection with mongo 2.2 | |
| 2449 | * | |
| 2450 | * query.select({ ids: { $elemMatch: { $in: [hexString] }}) | |
| 2451 | * | |
| 2452 | * @param {Object} fields | |
| 2453 | * @see https://github.com/LearnBoost/mongoose/issues/1091 | |
| 2454 | * @see http://docs.mongodb.org/manual/reference/projection/elemMatch/ | |
| 2455 | * @api private | |
| 2456 | */ | |
| 2457 | ||
| 2458 | 1 | Query.prototype._castFields = function _castFields (fields) { |
| 2459 | 0 | var selected |
| 2460 | , elemMatchKeys | |
| 2461 | , keys | |
| 2462 | , key | |
| 2463 | , out | |
| 2464 | , i | |
| 2465 | ||
| 2466 | 0 | if (fields) { |
| 2467 | 0 | keys = Object.keys(fields); |
| 2468 | 0 | elemMatchKeys = []; |
| 2469 | 0 | i = keys.length; |
| 2470 | ||
| 2471 | // collect $elemMatch args | |
| 2472 | 0 | while (i--) { |
| 2473 | 0 | key = keys[i]; |
| 2474 | 0 | if (fields[key].$elemMatch) { |
| 2475 | 0 | selected || (selected = {}); |
| 2476 | 0 | selected[key] = fields[key]; |
| 2477 | 0 | elemMatchKeys.push(key); |
| 2478 | } | |
| 2479 | } | |
| 2480 | } | |
| 2481 | ||
| 2482 | 0 | if (selected) { |
| 2483 | // they passed $elemMatch, cast em | |
| 2484 | 0 | try { |
| 2485 | 0 | out = this.cast(this.model, selected); |
| 2486 | } catch (err) { | |
| 2487 | 0 | return err; |
| 2488 | } | |
| 2489 | ||
| 2490 | // apply the casted field args | |
| 2491 | 0 | i = elemMatchKeys.length; |
| 2492 | 0 | while (i--) { |
| 2493 | 0 | key = elemMatchKeys[i]; |
| 2494 | 0 | fields[key] = out[key]; |
| 2495 | } | |
| 2496 | } | |
| 2497 | ||
| 2498 | 0 | return fields; |
| 2499 | } | |
| 2500 | ||
| 2501 | /** | |
| 2502 | * Returns a Node.js 0.8 style [read stream](http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream) interface. | |
| 2503 | * | |
| 2504 | * ####Example | |
| 2505 | * | |
| 2506 | * // follows the nodejs 0.8 stream api | |
| 2507 | * Thing.find({ name: /^hello/ }).stream().pipe(res) | |
| 2508 | * | |
| 2509 | * // manual streaming | |
| 2510 | * var stream = Thing.find({ name: /^hello/ }).stream(); | |
| 2511 | * | |
| 2512 | * stream.on('data', function (doc) { | |
| 2513 | * // do something with the mongoose document | |
| 2514 | * }).on('error', function (err) { | |
| 2515 | * // handle the error | |
| 2516 | * }).on('close', function () { | |
| 2517 | * // the stream is closed | |
| 2518 | * }); | |
| 2519 | * | |
| 2520 | * ####Valid options | |
| 2521 | * | |
| 2522 | * - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data`. | |
| 2523 | * | |
| 2524 | * ####Example | |
| 2525 | * | |
| 2526 | * // JSON.stringify all documents before emitting | |
| 2527 | * var stream = Thing.find().stream({ transform: JSON.stringify }); | |
| 2528 | * stream.pipe(writeStream); | |
| 2529 | * | |
| 2530 | * @return {QueryStream} | |
| 2531 | * @param {Object} [options] | |
| 2532 | * @see QueryStream | |
| 2533 | * @api public | |
| 2534 | */ | |
| 2535 | ||
| 2536 | 1 | Query.prototype.stream = function stream (opts) { |
| 2537 | 0 | return new QueryStream(this, opts); |
| 2538 | } | |
| 2539 | ||
| 2540 | // the rest of these are basically to support older Mongoose syntax with mquery | |
| 2541 | ||
| 2542 | /** | |
| 2543 | * _DEPRECATED_ Alias of `maxScan` | |
| 2544 | * | |
| 2545 | * @deprecated | |
| 2546 | * @see maxScan #query_Query-maxScan | |
| 2547 | * @method maxscan | |
| 2548 | * @memberOf Query | |
| 2549 | */ | |
| 2550 | ||
| 2551 | 1 | Query.prototype.maxscan = Query.base.maxScan; |
| 2552 | ||
| 2553 | /** | |
| 2554 | * Sets the tailable option (for use with capped collections). | |
| 2555 | * | |
| 2556 | * ####Example | |
| 2557 | * | |
| 2558 | * query.tailable() // true | |
| 2559 | * query.tailable(true) | |
| 2560 | * query.tailable(false) | |
| 2561 | * | |
| 2562 | * ####Note | |
| 2563 | * | |
| 2564 | * Cannot be used with `distinct()` | |
| 2565 | * | |
| 2566 | * @param {Boolean} bool defaults to true | |
| 2567 | * @see tailable http://docs.mongodb.org/manual/tutorial/create-tailable-cursor/ | |
| 2568 | * @api public | |
| 2569 | */ | |
| 2570 | ||
| 2571 | 1 | Query.prototype.tailable = function (val, opts) { |
| 2572 | // we need to support the tailable({ awaitdata : true }) as well as the | |
| 2573 | // tailable(true, {awaitdata :true}) syntax that mquery does not support | |
| 2574 | 0 | if (val && val.constructor.name == 'Object') { |
| 2575 | 0 | opts = val; |
| 2576 | 0 | val = true; |
| 2577 | } | |
| 2578 | ||
| 2579 | 0 | if (val === undefined) { |
| 2580 | 0 | val = true; |
| 2581 | } | |
| 2582 | ||
| 2583 | 0 | if (opts && opts.awaitdata) this.options.awaitdata = true; |
| 2584 | 0 | return Query.base.tailable.call(this, val); |
| 2585 | } | |
| 2586 | ||
| 2587 | /** | |
| 2588 | * Declares an intersects query for `geometry()`. | |
| 2589 | * | |
| 2590 | * ####Example | |
| 2591 | * | |
| 2592 | * query.where('path').intersects().geometry({ | |
| 2593 | * type: 'LineString' | |
| 2594 | * , coordinates: [[180.0, 11.0], [180, 9.0]] | |
| 2595 | * }) | |
| 2596 | * | |
| 2597 | * query.where('path').intersects({ | |
| 2598 | * type: 'LineString' | |
| 2599 | * , coordinates: [[180.0, 11.0], [180, 9.0]] | |
| 2600 | * }) | |
| 2601 | * | |
| 2602 | * ####NOTE: | |
| 2603 | * | |
| 2604 | * **MUST** be used after `where()`. | |
| 2605 | * | |
| 2606 | * ####NOTE: | |
| 2607 | * | |
| 2608 | * In Mongoose 3.7, `intersects` changed from a getter to a function. If you need the old syntax, use [this](https://github.com/ebensing/mongoose-within). | |
| 2609 | * | |
| 2610 | * @method intersects | |
| 2611 | * @memberOf Query | |
| 2612 | * @param {Object} [arg] | |
| 2613 | * @return {Query} this | |
| 2614 | * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ | |
| 2615 | * @see geoIntersects http://docs.mongodb.org/manual/reference/operator/geoIntersects/ | |
| 2616 | * @api public | |
| 2617 | */ | |
| 2618 | ||
| 2619 | /** | |
| 2620 | * Specifies a `$geometry` condition | |
| 2621 | * | |
| 2622 | * ####Example | |
| 2623 | * | |
| 2624 | * var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]] | |
| 2625 | * query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA }) | |
| 2626 | * | |
| 2627 | * // or | |
| 2628 | * var polyB = [[ 0, 0 ], [ 1, 1 ]] | |
| 2629 | * query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB }) | |
| 2630 | * | |
| 2631 | * // or | |
| 2632 | * var polyC = [ 0, 0 ] | |
| 2633 | * query.where('loc').within().geometry({ type: 'Point', coordinates: polyC }) | |
| 2634 | * | |
| 2635 | * // or | |
| 2636 | * query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC }) | |
| 2637 | * | |
| 2638 | * The argument is assigned to the most recent path passed to `where()`. | |
| 2639 | * | |
| 2640 | * ####NOTE: | |
| 2641 | * | |
| 2642 | * `geometry()` **must** come after either `intersects()` or `within()`. | |
| 2643 | * | |
| 2644 | * The `object` argument must contain `type` and `coordinates` properties. | |
| 2645 | * - type {String} | |
| 2646 | * - coordinates {Array} | |
| 2647 | * | |
| 2648 | * @method geometry | |
| 2649 | * @memberOf Query | |
| 2650 | * @param {Object} object Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the examples. | |
| 2651 | * @return {Query} this | |
| 2652 | * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ | |
| 2653 | * @see http://docs.mongodb.org/manual/release-notes/2.4/#new-geospatial-indexes-with-geojson-and-improved-spherical-geometry | |
| 2654 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2655 | * @api public | |
| 2656 | */ | |
| 2657 | ||
| 2658 | /** | |
| 2659 | * Specifies a `$near` or `$nearSphere` condition | |
| 2660 | * | |
| 2661 | * These operators return documents sorted by distance. | |
| 2662 | * | |
| 2663 | * ####Example | |
| 2664 | * | |
| 2665 | * query.where('loc').near({ center: [10, 10] }); | |
| 2666 | * query.where('loc').near({ center: [10, 10], maxDistance: 5 }); | |
| 2667 | * query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true }); | |
| 2668 | * query.near('loc', { center: [10, 10], maxDistance: 5 }); | |
| 2669 | * | |
| 2670 | * @method near | |
| 2671 | * @memberOf Query | |
| 2672 | * @param {String} [path] | |
| 2673 | * @param {Object} val | |
| 2674 | * @return {Query} this | |
| 2675 | * @see $near http://docs.mongodb.org/manual/reference/operator/near/ | |
| 2676 | * @see $nearSphere http://docs.mongodb.org/manual/reference/operator/nearSphere/ | |
| 2677 | * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ | |
| 2678 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2679 | * @api public | |
| 2680 | */ | |
| 2681 | ||
| 2682 | /*! | |
| 2683 | * Overwriting mquery is needed to support a couple different near() forms found in older | |
| 2684 | * versions of mongoose | |
| 2685 | * near([1,1]) | |
| 2686 | * near(1,1) | |
| 2687 | * near(field, [1,2]) | |
| 2688 | * near(field, 1, 2) | |
| 2689 | * In addition to all of the normal forms supported by mquery | |
| 2690 | */ | |
| 2691 | ||
| 2692 | 1 | Query.prototype.near = function () { |
| 2693 | 0 | var params = []; |
| 2694 | 0 | var sphere = this._mongooseOptions.nearSphere; |
| 2695 | ||
| 2696 | // TODO refactor | |
| 2697 | ||
| 2698 | 0 | if (arguments.length === 1) { |
| 2699 | 0 | if (Array.isArray(arguments[0])) { |
| 2700 | 0 | params.push({ center: arguments[0], spherical: sphere }); |
| 2701 | 0 | } else if ('string' == typeof arguments[0]) { |
| 2702 | // just passing a path | |
| 2703 | 0 | params.push(arguments[0]); |
| 2704 | 0 | } else if (utils.isObject(arguments[0])) { |
| 2705 | 0 | if ('boolean' != typeof arguments[0].spherical) { |
| 2706 | 0 | arguments[0].spherical = sphere; |
| 2707 | } | |
| 2708 | 0 | params.push(arguments[0]); |
| 2709 | } else { | |
| 2710 | 0 | throw new TypeError('invalid argument'); |
| 2711 | } | |
| 2712 | 0 | } else if (arguments.length === 2) { |
| 2713 | 0 | if ('number' == typeof arguments[0] && 'number' == typeof arguments[1]) { |
| 2714 | 0 | params.push({ center: [arguments[0], arguments[1]], spherical: sphere}); |
| 2715 | 0 | } else if ('string' == typeof arguments[0] && Array.isArray(arguments[1])) { |
| 2716 | 0 | params.push(arguments[0]); |
| 2717 | 0 | params.push({ center: arguments[1], spherical: sphere }); |
| 2718 | 0 | } else if ('string' == typeof arguments[0] && utils.isObject(arguments[1])) { |
| 2719 | 0 | params.push(arguments[0]); |
| 2720 | 0 | if ('boolean' != typeof arguments[1].spherical) { |
| 2721 | 0 | arguments[1].spherical = sphere; |
| 2722 | } | |
| 2723 | 0 | params.push(arguments[1]); |
| 2724 | } else { | |
| 2725 | 0 | throw new TypeError('invalid argument'); |
| 2726 | } | |
| 2727 | 0 | } else if (arguments.length === 3) { |
| 2728 | 0 | if ('string' == typeof arguments[0] && 'number' == typeof arguments[1] |
| 2729 | && 'number' == typeof arguments[2]) { | |
| 2730 | 0 | params.push(arguments[0]); |
| 2731 | 0 | params.push({ center: [arguments[1], arguments[2]], spherical: sphere }); |
| 2732 | } else { | |
| 2733 | 0 | throw new TypeError('invalid argument'); |
| 2734 | } | |
| 2735 | } else { | |
| 2736 | 0 | throw new TypeError('invalid argument'); |
| 2737 | } | |
| 2738 | ||
| 2739 | 0 | return Query.base.near.apply(this, params); |
| 2740 | } | |
| 2741 | ||
| 2742 | /** | |
| 2743 | * _DEPRECATED_ Specifies a `$nearSphere` condition | |
| 2744 | * | |
| 2745 | * ####Example | |
| 2746 | * | |
| 2747 | * query.where('loc').nearSphere({ center: [10, 10], maxDistance: 5 }); | |
| 2748 | * | |
| 2749 | * **Deprecated.** Use `query.near()` instead with the `spherical` option set to `true`. | |
| 2750 | * | |
| 2751 | * ####Example | |
| 2752 | * | |
| 2753 | * query.where('loc').near({ center: [10, 10], spherical: true }); | |
| 2754 | * | |
| 2755 | * @deprecated | |
| 2756 | * @see near() #query_Query-near | |
| 2757 | * @see $near http://docs.mongodb.org/manual/reference/operator/near/ | |
| 2758 | * @see $nearSphere http://docs.mongodb.org/manual/reference/operator/nearSphere/ | |
| 2759 | * @see $maxDistance http://docs.mongodb.org/manual/reference/operator/maxDistance/ | |
| 2760 | */ | |
| 2761 | ||
| 2762 | 1 | Query.prototype.nearSphere = function () { |
| 2763 | 0 | this._mongooseOptions.nearSphere = true; |
| 2764 | 0 | this.near.apply(this, arguments); |
| 2765 | 0 | return this; |
| 2766 | } | |
| 2767 | ||
| 2768 | /** | |
| 2769 | * Specifies a $polygon condition | |
| 2770 | * | |
| 2771 | * ####Example | |
| 2772 | * | |
| 2773 | * query.where('loc').within().polygon([10,20], [13, 25], [7,15]) | |
| 2774 | * query.polygon('loc', [10,20], [13, 25], [7,15]) | |
| 2775 | * | |
| 2776 | * @method polygon | |
| 2777 | * @memberOf Query | |
| 2778 | * @param {String|Array} [path] | |
| 2779 | * @param {Array|Object} [coordinatePairs...] | |
| 2780 | * @return {Query} this | |
| 2781 | * @see $polygon http://docs.mongodb.org/manual/reference/operator/polygon/ | |
| 2782 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2783 | * @api public | |
| 2784 | */ | |
| 2785 | ||
| 2786 | /** | |
| 2787 | * Specifies a $box condition | |
| 2788 | * | |
| 2789 | * ####Example | |
| 2790 | * | |
| 2791 | * var lowerLeft = [40.73083, -73.99756] | |
| 2792 | * var upperRight= [40.741404, -73.988135] | |
| 2793 | * | |
| 2794 | * query.where('loc').within().box(lowerLeft, upperRight) | |
| 2795 | * query.box({ ll : lowerLeft, ur : upperRight }) | |
| 2796 | * | |
| 2797 | * @method box | |
| 2798 | * @memberOf Query | |
| 2799 | * @see $box http://docs.mongodb.org/manual/reference/operator/box/ | |
| 2800 | * @see within() Query#within #query_Query-within | |
| 2801 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2802 | * @param {Object} val | |
| 2803 | * @param [Array] Upper Right Coords | |
| 2804 | * @return {Query} this | |
| 2805 | * @api public | |
| 2806 | */ | |
| 2807 | ||
| 2808 | /*! | |
| 2809 | * this is needed to support the mongoose syntax of: | |
| 2810 | * box(field, { ll : [x,y], ur : [x2,y2] }) | |
| 2811 | * box({ ll : [x,y], ur : [x2,y2] }) | |
| 2812 | */ | |
| 2813 | ||
| 2814 | 1 | Query.prototype.box = function (ll, ur) { |
| 2815 | 0 | if (!Array.isArray(ll) && utils.isObject(ll)) { |
| 2816 | 0 | ur = ll.ur; |
| 2817 | 0 | ll = ll.ll; |
| 2818 | } | |
| 2819 | 0 | return Query.base.box.call(this, ll, ur); |
| 2820 | } | |
| 2821 | ||
| 2822 | /** | |
| 2823 | * Specifies a $center or $centerSphere condition. | |
| 2824 | * | |
| 2825 | * ####Example | |
| 2826 | * | |
| 2827 | * var area = { center: [50, 50], radius: 10, unique: true } | |
| 2828 | * query.where('loc').within().circle(area) | |
| 2829 | * // alternatively | |
| 2830 | * query.circle('loc', area); | |
| 2831 | * | |
| 2832 | * // spherical calculations | |
| 2833 | * var area = { center: [50, 50], radius: 10, unique: true, spherical: true } | |
| 2834 | * query.where('loc').within().circle(area) | |
| 2835 | * // alternatively | |
| 2836 | * query.circle('loc', area); | |
| 2837 | * | |
| 2838 | * New in 3.7.0 | |
| 2839 | * | |
| 2840 | * @method circle | |
| 2841 | * @memberOf Query | |
| 2842 | * @param {String} [path] | |
| 2843 | * @param {Object} area | |
| 2844 | * @return {Query} this | |
| 2845 | * @see $center http://docs.mongodb.org/manual/reference/operator/center/ | |
| 2846 | * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ | |
| 2847 | * @see $geoWithin http://docs.mongodb.org/manual/reference/operator/within/ | |
| 2848 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2849 | * @api public | |
| 2850 | */ | |
| 2851 | ||
| 2852 | /** | |
| 2853 | * _DEPRECATED_ Alias for [circle](#query_Query-circle) | |
| 2854 | * | |
| 2855 | * **Deprecated.** Use [circle](#query_Query-circle) instead. | |
| 2856 | * | |
| 2857 | * @deprecated | |
| 2858 | * @method center | |
| 2859 | * @memberOf Query | |
| 2860 | * @api public | |
| 2861 | */ | |
| 2862 | ||
| 2863 | 1 | Query.prototype.center = Query.base.circle; |
| 2864 | ||
| 2865 | /** | |
| 2866 | * _DEPRECATED_ Specifies a $centerSphere condition | |
| 2867 | * | |
| 2868 | * **Deprecated.** Use [circle](#query_Query-circle) instead. | |
| 2869 | * | |
| 2870 | * ####Example | |
| 2871 | * | |
| 2872 | * var area = { center: [50, 50], radius: 10 }; | |
| 2873 | * query.where('loc').within().centerSphere(area); | |
| 2874 | * | |
| 2875 | * @deprecated | |
| 2876 | * @param {String} [path] | |
| 2877 | * @param {Object} val | |
| 2878 | * @return {Query} this | |
| 2879 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 2880 | * @see $centerSphere http://docs.mongodb.org/manual/reference/operator/centerSphere/ | |
| 2881 | * @api public | |
| 2882 | */ | |
| 2883 | ||
| 2884 | 1 | Query.prototype.centerSphere = function () { |
| 2885 | 0 | if (arguments[0] && arguments[0].constructor.name == 'Object') { |
| 2886 | 0 | arguments[0].spherical = true; |
| 2887 | } | |
| 2888 | ||
| 2889 | 0 | if (arguments[1] && arguments[1].constructor.name == 'Object') { |
| 2890 | 0 | arguments[1].spherical = true; |
| 2891 | } | |
| 2892 | ||
| 2893 | 0 | Query.base.circle.apply(this, arguments); |
| 2894 | } | |
| 2895 | ||
| 2896 | /** | |
| 2897 | * Determines if query fields are inclusive | |
| 2898 | * | |
| 2899 | * @return {Boolean} bool defaults to true | |
| 2900 | * @api private | |
| 2901 | */ | |
| 2902 | ||
| 2903 | 1 | Query.prototype._selectedInclusively = Query.prototype.selectedInclusively; |
| 2904 | ||
| 2905 | /*! | |
| 2906 | * Remove from public api for 3.8 | |
| 2907 | */ | |
| 2908 | ||
| 2909 | 1 | Query.prototype.selected = |
| 2910 | Query.prototype.selectedInclusively = | |
| 2911 | Query.prototype.selectedExclusively = undefined; | |
| 2912 | ||
| 2913 | /*! | |
| 2914 | * Export | |
| 2915 | */ | |
| 2916 | ||
| 2917 | 1 | module.exports = Query; |
| 2918 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('./utils') |
| 7 | ||
| 8 | /*! | |
| 9 | * Prepare a set of path options for query population. | |
| 10 | * | |
| 11 | * @param {Query} query | |
| 12 | * @param {Object} options | |
| 13 | * @return {Array} | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | exports.preparePopulationOptions = function preparePopulationOptions (query, options) { |
| 17 | 0 | var pop = utils.object.vals(query.options.populate); |
| 18 | ||
| 19 | // lean options should trickle through all queries | |
| 20 | 0 | if (options.lean) pop.forEach(makeLean); |
| 21 | ||
| 22 | 0 | return pop; |
| 23 | } | |
| 24 | ||
| 25 | /*! | |
| 26 | * Prepare a set of path options for query population. This is the MongooseQuery | |
| 27 | * version | |
| 28 | * | |
| 29 | * @param {Query} query | |
| 30 | * @param {Object} options | |
| 31 | * @return {Array} | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | exports.preparePopulationOptionsMQ = function preparePopulationOptionsMQ (query, options) { |
| 35 | 0 | var pop = utils.object.vals(query._mongooseOptions.populate); |
| 36 | ||
| 37 | // lean options should trickle through all queries | |
| 38 | 0 | if (options.lean) pop.forEach(makeLean); |
| 39 | ||
| 40 | 0 | return pop; |
| 41 | } | |
| 42 | ||
| 43 | /*! | |
| 44 | * If the document is a mapped discriminator type, it returns a model instance for that type, otherwise, | |
| 45 | * it returns an instance of the given model. | |
| 46 | * | |
| 47 | * @param {Model} model | |
| 48 | * @param {Object} doc | |
| 49 | * @param {Object} fields | |
| 50 | * | |
| 51 | * @return {Model} | |
| 52 | */ | |
| 53 | 1 | exports.createModel = function createModel(model, doc, fields) { |
| 54 | 0 | var discriminatorMapping = model.schema |
| 55 | ? model.schema.discriminatorMapping | |
| 56 | : null; | |
| 57 | ||
| 58 | 0 | var key = discriminatorMapping && discriminatorMapping.isRoot |
| 59 | ? discriminatorMapping.key | |
| 60 | : null; | |
| 61 | ||
| 62 | 0 | if (key && doc[key] && model.discriminators && model.discriminators[doc[key]]) { |
| 63 | 0 | return new model.discriminators[doc[key]](undefined, fields, true); |
| 64 | } | |
| 65 | ||
| 66 | 0 | return new model(undefined, fields, true); |
| 67 | } | |
| 68 | ||
| 69 | /*! | |
| 70 | * Set each path query option to lean | |
| 71 | * | |
| 72 | * @param {Object} option | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | function makeLean (option) { |
| 76 | 0 | option.options || (option.options = {}); |
| 77 | 0 | option.options.lean = true; |
| 78 | } | |
| 79 | ||
| 80 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Stream = require('stream').Stream |
| 7 | 1 | var utils = require('./utils') |
| 8 | 1 | var helpers = require('./queryhelpers') |
| 9 | 1 | var K = function(k){ return k } |
| 10 | ||
| 11 | /** | |
| 12 | * Provides a Node.js 0.8 style [ReadStream](http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream) interface for Queries. | |
| 13 | * | |
| 14 | * var stream = Model.find().stream(); | |
| 15 | * | |
| 16 | * stream.on('data', function (doc) { | |
| 17 | * // do something with the mongoose document | |
| 18 | * }).on('error', function (err) { | |
| 19 | * // handle the error | |
| 20 | * }).on('close', function () { | |
| 21 | * // the stream is closed | |
| 22 | * }); | |
| 23 | * | |
| 24 | * | |
| 25 | * The stream interface allows us to simply "plug-in" to other _Node.js 0.8_ style write streams. | |
| 26 | * | |
| 27 | * Model.where('created').gte(twoWeeksAgo).stream().pipe(writeStream); | |
| 28 | * | |
| 29 | * ####Valid options | |
| 30 | * | |
| 31 | * - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data`. | |
| 32 | * | |
| 33 | * ####Example | |
| 34 | * | |
| 35 | * // JSON.stringify all documents before emitting | |
| 36 | * var stream = Thing.find().stream({ transform: JSON.stringify }); | |
| 37 | * stream.pipe(writeStream); | |
| 38 | * | |
| 39 | * _NOTE: plugging into an HTTP response will *not* work out of the box. Those streams expect only strings or buffers to be emitted, so first formatting our documents as strings/buffers is necessary._ | |
| 40 | * | |
| 41 | * _NOTE: these streams are Node.js 0.8 style read streams which differ from Node.js 0.10 style. Node.js 0.10 streams are not well tested yet and are not guaranteed to work._ | |
| 42 | * | |
| 43 | * @param {Query} query | |
| 44 | * @param {Object} [options] | |
| 45 | * @inherits NodeJS Stream http://nodejs.org/docs/v0.8.21/api/stream.html#stream_readable_stream | |
| 46 | * @event `data`: emits a single Mongoose document | |
| 47 | * @event `error`: emits when an error occurs during streaming. This will emit _before_ the `close` event. | |
| 48 | * @event `close`: emits when the stream reaches the end of the cursor or an error occurs, or the stream is manually `destroy`ed. After this event, no more events are emitted. | |
| 49 | * @api public | |
| 50 | */ | |
| 51 | ||
| 52 | 1 | function QueryStream (query, options) { |
| 53 | 0 | Stream.call(this); |
| 54 | ||
| 55 | 0 | this.query = query; |
| 56 | 0 | this.readable = true; |
| 57 | 0 | this.paused = false; |
| 58 | 0 | this._cursor = null; |
| 59 | 0 | this._destroyed = null; |
| 60 | 0 | this._fields = null; |
| 61 | 0 | this._buffer = null; |
| 62 | 0 | this._inline = T_INIT; |
| 63 | 0 | this._running = false; |
| 64 | 0 | this._transform = options && 'function' == typeof options.transform |
| 65 | ? options.transform | |
| 66 | : K; | |
| 67 | ||
| 68 | // give time to hook up events | |
| 69 | 0 | var self = this; |
| 70 | 0 | process.nextTick(function () { |
| 71 | 0 | self._init(); |
| 72 | }); | |
| 73 | } | |
| 74 | ||
| 75 | /*! | |
| 76 | * Inherit from Stream | |
| 77 | */ | |
| 78 | ||
| 79 | 1 | QueryStream.prototype.__proto__ = Stream.prototype; |
| 80 | ||
| 81 | /** | |
| 82 | * Flag stating whether or not this stream is readable. | |
| 83 | * | |
| 84 | * @property readable | |
| 85 | * @api public | |
| 86 | */ | |
| 87 | ||
| 88 | 1 | QueryStream.prototype.readable; |
| 89 | ||
| 90 | /** | |
| 91 | * Flag stating whether or not this stream is paused. | |
| 92 | * | |
| 93 | * @property paused | |
| 94 | * @api public | |
| 95 | */ | |
| 96 | ||
| 97 | 1 | QueryStream.prototype.paused; |
| 98 | ||
| 99 | // trampoline flags | |
| 100 | 1 | var T_INIT = 0; |
| 101 | 1 | var T_IDLE = 1; |
| 102 | 1 | var T_CONT = 2; |
| 103 | ||
| 104 | /** | |
| 105 | * Initializes the query. | |
| 106 | * | |
| 107 | * @api private | |
| 108 | */ | |
| 109 | ||
| 110 | 1 | QueryStream.prototype._init = function () { |
| 111 | 0 | if (this._destroyed) return; |
| 112 | ||
| 113 | 0 | var query = this.query |
| 114 | , model = query.model | |
| 115 | , options = query._optionsForExec(model) | |
| 116 | , self = this | |
| 117 | ||
| 118 | 0 | try { |
| 119 | 0 | query.cast(model); |
| 120 | } catch (err) { | |
| 121 | 0 | return self.destroy(err); |
| 122 | } | |
| 123 | ||
| 124 | 0 | self._fields = utils.clone(query._fields); |
| 125 | 0 | options.fields = query._castFields(self._fields); |
| 126 | ||
| 127 | 0 | model.collection.find(query._conditions, options, function (err, cursor) { |
| 128 | 0 | if (err) return self.destroy(err); |
| 129 | 0 | self._cursor = cursor; |
| 130 | 0 | self._next(); |
| 131 | }); | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * Trampoline for pulling the next doc from cursor. | |
| 136 | * | |
| 137 | * @see QueryStream#__next #querystream_QueryStream-__next | |
| 138 | * @api private | |
| 139 | */ | |
| 140 | ||
| 141 | 1 | QueryStream.prototype._next = function _next () { |
| 142 | 0 | if (this.paused || this._destroyed) { |
| 143 | 0 | return this._running = false; |
| 144 | } | |
| 145 | ||
| 146 | 0 | this._running = true; |
| 147 | ||
| 148 | 0 | if (this._buffer && this._buffer.length) { |
| 149 | 0 | var arg; |
| 150 | 0 | while (!this.paused && !this._destroyed && (arg = this._buffer.shift())) { |
| 151 | 0 | this._onNextObject.apply(this, arg); |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | // avoid stack overflows with large result sets. | |
| 156 | // trampoline instead of recursion. | |
| 157 | 0 | while (this.__next()) {} |
| 158 | } | |
| 159 | ||
| 160 | /** | |
| 161 | * Pulls the next doc from the cursor. | |
| 162 | * | |
| 163 | * @see QueryStream#_next #querystream_QueryStream-_next | |
| 164 | * @api private | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | QueryStream.prototype.__next = function () { |
| 168 | 0 | if (this.paused || this._destroyed) |
| 169 | 0 | return this._running = false; |
| 170 | ||
| 171 | 0 | var self = this; |
| 172 | 0 | self._inline = T_INIT; |
| 173 | ||
| 174 | 0 | self._cursor.nextObject(function cursorcb (err, doc) { |
| 175 | 0 | self._onNextObject(err, doc); |
| 176 | }); | |
| 177 | ||
| 178 | // if onNextObject() was already called in this tick | |
| 179 | // return ourselves to the trampoline. | |
| 180 | 0 | if (T_CONT === this._inline) { |
| 181 | 0 | return true; |
| 182 | } else { | |
| 183 | // onNextObject() hasn't fired yet. tell onNextObject | |
| 184 | // that its ok to call _next b/c we are not within | |
| 185 | // the trampoline anymore. | |
| 186 | 0 | this._inline = T_IDLE; |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /** | |
| 191 | * Transforms raw `doc`s returned from the cursor into a model instance. | |
| 192 | * | |
| 193 | * @param {Error|null} err | |
| 194 | * @param {Object} doc | |
| 195 | * @api private | |
| 196 | */ | |
| 197 | ||
| 198 | 1 | QueryStream.prototype._onNextObject = function _onNextObject (err, doc) { |
| 199 | 0 | if (this._destroyed) return; |
| 200 | ||
| 201 | 0 | if (this.paused) { |
| 202 | 0 | this._buffer || (this._buffer = []); |
| 203 | 0 | this._buffer.push([err, doc]); |
| 204 | 0 | return this._running = false; |
| 205 | } | |
| 206 | ||
| 207 | 0 | if (err) return this.destroy(err); |
| 208 | ||
| 209 | // when doc is null we hit the end of the cursor | |
| 210 | 0 | if (!doc) { |
| 211 | 0 | this.emit('end'); |
| 212 | 0 | return this.destroy(); |
| 213 | } | |
| 214 | ||
| 215 | 0 | var opts = this.query._mongooseOptions; |
| 216 | ||
| 217 | 0 | if (!opts.populate) { |
| 218 | 0 | return true === opts.lean |
| 219 | ? emit(this, doc) | |
| 220 | : createAndEmit(this, doc); | |
| 221 | } | |
| 222 | ||
| 223 | 0 | var self = this; |
| 224 | 0 | var pop = helpers.preparePopulationOptionsMQ(self.query, self.query._mongooseOptions); |
| 225 | ||
| 226 | 0 | self.query.model.populate(doc, pop, function (err, doc) { |
| 227 | 0 | if (err) return self.destroy(err); |
| 228 | 0 | return true === opts.lean |
| 229 | ? emit(self, doc) | |
| 230 | : createAndEmit(self, doc); | |
| 231 | }) | |
| 232 | } | |
| 233 | ||
| 234 | 1 | function createAndEmit (self, doc) { |
| 235 | 0 | var instance = helpers.createModel(self.query.model, doc, self._fields); |
| 236 | ||
| 237 | 0 | instance.init(doc, function (err) { |
| 238 | 0 | if (err) return self.destroy(err); |
| 239 | 0 | emit(self, instance); |
| 240 | }); | |
| 241 | } | |
| 242 | ||
| 243 | /*! | |
| 244 | * Emit a data event and manage the trampoline state | |
| 245 | */ | |
| 246 | ||
| 247 | 1 | function emit (self, doc) { |
| 248 | 0 | self.emit('data', self._transform(doc)); |
| 249 | ||
| 250 | // trampoline management | |
| 251 | 0 | if (T_IDLE === self._inline) { |
| 252 | // no longer in trampoline. restart it. | |
| 253 | 0 | self._next(); |
| 254 | } else { | |
| 255 | // in a trampoline. tell __next that its | |
| 256 | // ok to continue jumping. | |
| 257 | 0 | self._inline = T_CONT; |
| 258 | } | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Pauses this stream. | |
| 263 | * | |
| 264 | * @api public | |
| 265 | */ | |
| 266 | ||
| 267 | 1 | QueryStream.prototype.pause = function () { |
| 268 | 0 | this.paused = true; |
| 269 | } | |
| 270 | ||
| 271 | /** | |
| 272 | * Resumes this stream. | |
| 273 | * | |
| 274 | * @api public | |
| 275 | */ | |
| 276 | ||
| 277 | 1 | QueryStream.prototype.resume = function () { |
| 278 | 0 | this.paused = false; |
| 279 | ||
| 280 | 0 | if (!this._cursor) { |
| 281 | // cannot start if not initialized | |
| 282 | 0 | return; |
| 283 | } | |
| 284 | ||
| 285 | // are we within the trampoline? | |
| 286 | 0 | if (T_INIT === this._inline) { |
| 287 | 0 | return; |
| 288 | } | |
| 289 | ||
| 290 | 0 | if (!this._running) { |
| 291 | // outside QueryStream control, need manual restart | |
| 292 | 0 | return this._next(); |
| 293 | } | |
| 294 | } | |
| 295 | ||
| 296 | /** | |
| 297 | * Destroys the stream, closing the underlying cursor. No more events will be emitted. | |
| 298 | * | |
| 299 | * @param {Error} [err] | |
| 300 | * @api public | |
| 301 | */ | |
| 302 | ||
| 303 | 1 | QueryStream.prototype.destroy = function (err) { |
| 304 | 0 | if (this._destroyed) return; |
| 305 | 0 | this._destroyed = true; |
| 306 | 0 | this._running = false; |
| 307 | 0 | this.readable = false; |
| 308 | ||
| 309 | 0 | if (this._cursor) { |
| 310 | 0 | this._cursor.close(); |
| 311 | } | |
| 312 | ||
| 313 | 0 | if (err) { |
| 314 | 0 | this.emit('error', err); |
| 315 | } | |
| 316 | ||
| 317 | 0 | this.emit('close'); |
| 318 | } | |
| 319 | ||
| 320 | /** | |
| 321 | * Pipes this query stream into another stream. This method is inherited from NodeJS Streams. | |
| 322 | * | |
| 323 | * ####Example: | |
| 324 | * | |
| 325 | * query.stream().pipe(writeStream [, options]) | |
| 326 | * | |
| 327 | * @method pipe | |
| 328 | * @memberOf QueryStream | |
| 329 | * @see NodeJS http://nodejs.org/api/stream.html | |
| 330 | * @api public | |
| 331 | */ | |
| 332 | ||
| 333 | /*! | |
| 334 | * Module exports | |
| 335 | */ | |
| 336 | ||
| 337 | 1 | module.exports = exports = QueryStream; |
| 338 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var EventEmitter = require('events').EventEmitter |
| 6 | , VirtualType = require('./virtualtype') | |
| 7 | , utils = require('./utils') | |
| 8 | , mquery = require('mquery') | |
| 9 | , Query | |
| 10 | , Types | |
| 11 | ||
| 12 | /** | |
| 13 | * Schema constructor. | |
| 14 | * | |
| 15 | * ####Example: | |
| 16 | * | |
| 17 | * var child = new Schema({ name: String }); | |
| 18 | * var schema = new Schema({ name: String, age: Number, children: [child] }); | |
| 19 | * var Tree = mongoose.model('Tree', schema); | |
| 20 | * | |
| 21 | * // setting schema options | |
| 22 | * new Schema({ name: String }, { _id: false, autoIndex: false }) | |
| 23 | * | |
| 24 | * ####Options: | |
| 25 | * | |
| 26 | * - [autoIndex](/docs/guide.html#autoIndex): bool - defaults to true | |
| 27 | * - [bufferCommands](/docs/guide.html#bufferCommands): bool - defaults to true | |
| 28 | * - [capped](/docs/guide.html#capped): bool - defaults to false | |
| 29 | * - [collection](/docs/guide.html#collection): string - no default | |
| 30 | * - [id](/docs/guide.html#id): bool - defaults to true | |
| 31 | * - [_id](/docs/guide.html#_id): bool - defaults to true | |
| 32 | * - `minimize`: bool - controls [document#toObject](#document_Document-toObject) behavior when called manually - defaults to true | |
| 33 | * - [read](/docs/guide.html#read): string | |
| 34 | * - [safe](/docs/guide.html#safe): bool - defaults to true. | |
| 35 | * - [shardKey](/docs/guide.html#shardKey): bool - defaults to `null` | |
| 36 | * - [strict](/docs/guide.html#strict): bool - defaults to true | |
| 37 | * - [toJSON](/docs/guide.html#toJSON) - object - no default | |
| 38 | * - [toObject](/docs/guide.html#toObject) - object - no default | |
| 39 | * - [versionKey](/docs/guide.html#versionKey): bool - defaults to "__v" | |
| 40 | * | |
| 41 | * ####Note: | |
| 42 | * | |
| 43 | * _When nesting schemas, (`children` in the example above), always declare the child schema first before passing it into is parent._ | |
| 44 | * | |
| 45 | * @param {Object} definition | |
| 46 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter | |
| 47 | * @event `init`: Emitted after the schema is compiled into a `Model`. | |
| 48 | * @api public | |
| 49 | */ | |
| 50 | ||
| 51 | 1 | function Schema (obj, options) { |
| 52 | 1 | if (!(this instanceof Schema)) |
| 53 | 0 | return new Schema(obj, options); |
| 54 | ||
| 55 | 1 | this.paths = {}; |
| 56 | 1 | this.subpaths = {}; |
| 57 | 1 | this.virtuals = {}; |
| 58 | 1 | this.nested = {}; |
| 59 | 1 | this.inherits = {}; |
| 60 | 1 | this.callQueue = []; |
| 61 | 1 | this._indexes = []; |
| 62 | 1 | this.methods = {}; |
| 63 | 1 | this.statics = {}; |
| 64 | 1 | this.tree = {}; |
| 65 | 1 | this._requiredpaths = undefined; |
| 66 | 1 | this.discriminatorMapping = undefined; |
| 67 | 1 | this._indexedpaths = undefined; |
| 68 | ||
| 69 | 1 | this.options = this.defaultOptions(options); |
| 70 | ||
| 71 | // build paths | |
| 72 | 1 | if (obj) { |
| 73 | 1 | this.add(obj); |
| 74 | } | |
| 75 | ||
| 76 | // ensure the documents get an auto _id unless disabled | |
| 77 | 1 | var auto_id = !this.paths['_id'] && (!this.options.noId && this.options._id); |
| 78 | 1 | if (auto_id) { |
| 79 | 0 | this.add({ _id: {type: Schema.ObjectId, auto: true} }); |
| 80 | } | |
| 81 | ||
| 82 | // ensure the documents receive an id getter unless disabled | |
| 83 | 1 | var autoid = !this.paths['id'] && (!this.options.noVirtualId && this.options.id); |
| 84 | 1 | if (autoid) { |
| 85 | 0 | this.virtual('id').get(idGetter); |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | /*! | |
| 90 | * Returns this documents _id cast to a string. | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function idGetter () { |
| 94 | 0 | if (this.$__._id) { |
| 95 | 0 | return this.$__._id; |
| 96 | } | |
| 97 | ||
| 98 | 0 | return this.$__._id = null == this._id |
| 99 | ? null | |
| 100 | : String(this._id); | |
| 101 | } | |
| 102 | ||
| 103 | /*! | |
| 104 | * Inherit from EventEmitter. | |
| 105 | */ | |
| 106 | ||
| 107 | 1 | Schema.prototype.__proto__ = EventEmitter.prototype; |
| 108 | ||
| 109 | /** | |
| 110 | * Schema as flat paths | |
| 111 | * | |
| 112 | * ####Example: | |
| 113 | * { | |
| 114 | * '_id' : SchemaType, | |
| 115 | * , 'nested.key' : SchemaType, | |
| 116 | * } | |
| 117 | * | |
| 118 | * @api private | |
| 119 | * @property paths | |
| 120 | */ | |
| 121 | ||
| 122 | 1 | Schema.prototype.paths; |
| 123 | ||
| 124 | /** | |
| 125 | * Schema as a tree | |
| 126 | * | |
| 127 | * ####Example: | |
| 128 | * { | |
| 129 | * '_id' : ObjectId | |
| 130 | * , 'nested' : { | |
| 131 | * 'key' : String | |
| 132 | * } | |
| 133 | * } | |
| 134 | * | |
| 135 | * @api private | |
| 136 | * @property tree | |
| 137 | */ | |
| 138 | ||
| 139 | 1 | Schema.prototype.tree; |
| 140 | ||
| 141 | /** | |
| 142 | * Returns default options for this schema, merged with `options`. | |
| 143 | * | |
| 144 | * @param {Object} options | |
| 145 | * @return {Object} | |
| 146 | * @api private | |
| 147 | */ | |
| 148 | ||
| 149 | 1 | Schema.prototype.defaultOptions = function (options) { |
| 150 | 1 | if (options && false === options.safe) { |
| 151 | 0 | options.safe = { w: 0 }; |
| 152 | } | |
| 153 | ||
| 154 | 1 | if (options && options.safe && 0 === options.safe.w) { |
| 155 | // if you turn off safe writes, then versioning goes off as well | |
| 156 | 0 | options.versionKey = false; |
| 157 | } | |
| 158 | ||
| 159 | 1 | options = utils.options({ |
| 160 | strict: true | |
| 161 | , bufferCommands: true | |
| 162 | , capped: false // { size, max, autoIndexId } | |
| 163 | , versionKey: '__v' | |
| 164 | , discriminatorKey: '__t' | |
| 165 | , minimize: true | |
| 166 | , autoIndex: true | |
| 167 | , shardKey: null | |
| 168 | , read: null | |
| 169 | // the following are only applied at construction time | |
| 170 | , noId: false // deprecated, use { _id: false } | |
| 171 | , _id: true | |
| 172 | , noVirtualId: false // deprecated, use { id: false } | |
| 173 | , id: true | |
| 174 | // , pluralization: true // only set this to override the global option | |
| 175 | }, options); | |
| 176 | ||
| 177 | 1 | if (options.read) { |
| 178 | 0 | options.read = utils.readPref(options.read); |
| 179 | } | |
| 180 | ||
| 181 | 1 | return options; |
| 182 | } | |
| 183 | ||
| 184 | /** | |
| 185 | * Adds key path / schema type pairs to this schema. | |
| 186 | * | |
| 187 | * ####Example: | |
| 188 | * | |
| 189 | * var ToySchema = new Schema; | |
| 190 | * ToySchema.add({ name: 'string', color: 'string', price: 'number' }); | |
| 191 | * | |
| 192 | * @param {Object} obj | |
| 193 | * @param {String} prefix | |
| 194 | * @api public | |
| 195 | */ | |
| 196 | ||
| 197 | 1 | Schema.prototype.add = function add (obj, prefix) { |
| 198 | 1 | prefix = prefix || ''; |
| 199 | 1 | var keys = Object.keys(obj); |
| 200 | ||
| 201 | 1 | for (var i = 0; i < keys.length; ++i) { |
| 202 | 17 | var key = keys[i]; |
| 203 | ||
| 204 | 17 | if (null == obj[key]) { |
| 205 | 0 | throw new TypeError('Invalid value for schema path `'+ prefix + key +'`'); |
| 206 | } | |
| 207 | ||
| 208 | 17 | if (utils.isObject(obj[key]) && (!obj[key].constructor || 'Object' == obj[key].constructor.name) && (!obj[key].type || obj[key].type.type)) { |
| 209 | 0 | if (Object.keys(obj[key]).length) { |
| 210 | // nested object { last: { name: String }} | |
| 211 | 0 | this.nested[prefix + key] = true; |
| 212 | 0 | this.add(obj[key], prefix + key + '.'); |
| 213 | } else { | |
| 214 | 0 | this.path(prefix + key, obj[key]); // mixed type |
| 215 | } | |
| 216 | } else { | |
| 217 | 17 | this.path(prefix + key, obj[key]); |
| 218 | } | |
| 219 | } | |
| 220 | }; | |
| 221 | ||
| 222 | /** | |
| 223 | * Reserved document keys. | |
| 224 | * | |
| 225 | * Keys in this object are names that are rejected in schema declarations b/c they conflict with mongoose functionality. Using these key name will throw an error. | |
| 226 | * | |
| 227 | * on, emit, _events, db, get, set, init, isNew, errors, schema, options, modelName, collection, _pres, _posts, toObject | |
| 228 | * | |
| 229 | * _NOTE:_ Use of these terms as method names is permitted, but play at your own risk, as they may be existing mongoose document methods you are stomping on. | |
| 230 | * | |
| 231 | * var schema = new Schema(..); | |
| 232 | * schema.methods.init = function () {} // potentially breaking | |
| 233 | */ | |
| 234 | ||
| 235 | 1 | Schema.reserved = Object.create(null); |
| 236 | 1 | var reserved = Schema.reserved; |
| 237 | 1 | reserved.on = |
| 238 | reserved.db = | |
| 239 | reserved.set = | |
| 240 | reserved.get = | |
| 241 | reserved.init = | |
| 242 | reserved.isNew = | |
| 243 | reserved.errors = | |
| 244 | reserved.schema = | |
| 245 | reserved.options = | |
| 246 | reserved.modelName = | |
| 247 | reserved.collection = | |
| 248 | reserved.toObject = | |
| 249 | reserved.emit = // EventEmitter | |
| 250 | reserved._events = // EventEmitter | |
| 251 | reserved._pres = reserved._posts = 1 // hooks.js | |
| 252 | ||
| 253 | /** | |
| 254 | * Gets/sets schema paths. | |
| 255 | * | |
| 256 | * Sets a path (if arity 2) | |
| 257 | * Gets a path (if arity 1) | |
| 258 | * | |
| 259 | * ####Example | |
| 260 | * | |
| 261 | * schema.path('name') // returns a SchemaType | |
| 262 | * schema.path('name', Number) // changes the schemaType of `name` to Number | |
| 263 | * | |
| 264 | * @param {String} path | |
| 265 | * @param {Object} constructor | |
| 266 | * @api public | |
| 267 | */ | |
| 268 | ||
| 269 | 1 | Schema.prototype.path = function (path, obj) { |
| 270 | 17 | if (obj == undefined) { |
| 271 | 0 | if (this.paths[path]) return this.paths[path]; |
| 272 | 0 | if (this.subpaths[path]) return this.subpaths[path]; |
| 273 | ||
| 274 | // subpaths? | |
| 275 | 0 | return /\.\d+\.?.*$/.test(path) |
| 276 | ? getPositionalPath(this, path) | |
| 277 | : undefined; | |
| 278 | } | |
| 279 | ||
| 280 | // some path names conflict with document methods | |
| 281 | 17 | if (reserved[path]) { |
| 282 | 0 | throw new Error("`" + path + "` may not be used as a schema pathname"); |
| 283 | } | |
| 284 | ||
| 285 | // update the tree | |
| 286 | 17 | var subpaths = path.split(/\./) |
| 287 | , last = subpaths.pop() | |
| 288 | , branch = this.tree; | |
| 289 | ||
| 290 | 17 | subpaths.forEach(function(sub, i) { |
| 291 | 0 | if (!branch[sub]) branch[sub] = {}; |
| 292 | 0 | if ('object' != typeof branch[sub]) { |
| 293 | 0 | var msg = 'Cannot set nested path `' + path + '`. ' |
| 294 | + 'Parent path `' | |
| 295 | + subpaths.slice(0, i).concat([sub]).join('.') | |
| 296 | + '` already set to type ' + branch[sub].name | |
| 297 | + '.'; | |
| 298 | 0 | throw new Error(msg); |
| 299 | } | |
| 300 | 0 | branch = branch[sub]; |
| 301 | }); | |
| 302 | ||
| 303 | 17 | branch[last] = utils.clone(obj); |
| 304 | ||
| 305 | 17 | this.paths[path] = Schema.interpretAsType(path, obj); |
| 306 | 17 | return this; |
| 307 | }; | |
| 308 | ||
| 309 | /** | |
| 310 | * Converts type arguments into Mongoose Types. | |
| 311 | * | |
| 312 | * @param {String} path | |
| 313 | * @param {Object} obj constructor | |
| 314 | * @api private | |
| 315 | */ | |
| 316 | ||
| 317 | 1 | Schema.interpretAsType = function (path, obj) { |
| 318 | 17 | if (obj.constructor && obj.constructor.name != 'Object') |
| 319 | 17 | obj = { type: obj }; |
| 320 | ||
| 321 | // Get the type making sure to allow keys named "type" | |
| 322 | // and default to mixed if not specified. | |
| 323 | // { type: { type: String, default: 'freshcut' } } | |
| 324 | 17 | var type = obj.type && !obj.type.type |
| 325 | ? obj.type | |
| 326 | : {}; | |
| 327 | ||
| 328 | 17 | if ('Object' == type.constructor.name || 'mixed' == type) { |
| 329 | 0 | return new Types.Mixed(path, obj); |
| 330 | } | |
| 331 | ||
| 332 | 17 | if (Array.isArray(type) || Array == type || 'array' == type) { |
| 333 | // if it was specified through { type } look for `cast` | |
| 334 | 0 | var cast = (Array == type || 'array' == type) |
| 335 | ? obj.cast | |
| 336 | : type[0]; | |
| 337 | ||
| 338 | 0 | if (cast instanceof Schema) { |
| 339 | 0 | return new Types.DocumentArray(path, cast, obj); |
| 340 | } | |
| 341 | ||
| 342 | 0 | if ('string' == typeof cast) { |
| 343 | 0 | cast = Types[cast.charAt(0).toUpperCase() + cast.substring(1)]; |
| 344 | 0 | } else if (cast && (!cast.type || cast.type.type) |
| 345 | && 'Object' == cast.constructor.name | |
| 346 | && Object.keys(cast).length) { | |
| 347 | 0 | return new Types.DocumentArray(path, new Schema(cast), obj); |
| 348 | } | |
| 349 | ||
| 350 | 0 | return new Types.Array(path, cast || Types.Mixed, obj); |
| 351 | } | |
| 352 | ||
| 353 | 17 | var name = 'string' == typeof type |
| 354 | ? type | |
| 355 | : type.name; | |
| 356 | ||
| 357 | 17 | if (name) { |
| 358 | 17 | name = name.charAt(0).toUpperCase() + name.substring(1); |
| 359 | } | |
| 360 | ||
| 361 | 17 | if (undefined == Types[name]) { |
| 362 | 0 | throw new TypeError('Undefined type at `' + path + |
| 363 | '`\n Did you try nesting Schemas? ' + | |
| 364 | 'You can only nest using refs or arrays.'); | |
| 365 | } | |
| 366 | ||
| 367 | 17 | return new Types[name](path, obj); |
| 368 | }; | |
| 369 | ||
| 370 | /** | |
| 371 | * Iterates the schemas paths similar to Array#forEach. | |
| 372 | * | |
| 373 | * The callback is passed the pathname and schemaType as arguments on each iteration. | |
| 374 | * | |
| 375 | * @param {Function} fn callback function | |
| 376 | * @return {Schema} this | |
| 377 | * @api public | |
| 378 | */ | |
| 379 | ||
| 380 | 1 | Schema.prototype.eachPath = function (fn) { |
| 381 | 0 | var keys = Object.keys(this.paths) |
| 382 | , len = keys.length; | |
| 383 | ||
| 384 | 0 | for (var i = 0; i < len; ++i) { |
| 385 | 0 | fn(keys[i], this.paths[keys[i]]); |
| 386 | } | |
| 387 | ||
| 388 | 0 | return this; |
| 389 | }; | |
| 390 | ||
| 391 | /** | |
| 392 | * Returns an Array of path strings that are required by this schema. | |
| 393 | * | |
| 394 | * @api public | |
| 395 | * @return {Array} | |
| 396 | */ | |
| 397 | ||
| 398 | 1 | Schema.prototype.requiredPaths = function requiredPaths () { |
| 399 | 0 | if (this._requiredpaths) return this._requiredpaths; |
| 400 | ||
| 401 | 0 | var paths = Object.keys(this.paths) |
| 402 | , i = paths.length | |
| 403 | , ret = []; | |
| 404 | ||
| 405 | 0 | while (i--) { |
| 406 | 0 | var path = paths[i]; |
| 407 | 0 | if (this.paths[path].isRequired) ret.push(path); |
| 408 | } | |
| 409 | ||
| 410 | 0 | return this._requiredpaths = ret; |
| 411 | } | |
| 412 | ||
| 413 | /** | |
| 414 | * Returns indexes from fields and schema-level indexes (cached). | |
| 415 | * | |
| 416 | * @api private | |
| 417 | * @return {Array} | |
| 418 | */ | |
| 419 | ||
| 420 | 1 | Schema.prototype.indexedPaths = function indexedPaths () { |
| 421 | 0 | if (this._indexedpaths) return this._indexedpaths; |
| 422 | ||
| 423 | 0 | return this._indexedpaths = this.indexes(); |
| 424 | } | |
| 425 | ||
| 426 | /** | |
| 427 | * Returns the pathType of `path` for this schema. | |
| 428 | * | |
| 429 | * Given a path, returns whether it is a real, virtual, nested, or ad-hoc/undefined path. | |
| 430 | * | |
| 431 | * @param {String} path | |
| 432 | * @return {String} | |
| 433 | * @api public | |
| 434 | */ | |
| 435 | ||
| 436 | 1 | Schema.prototype.pathType = function (path) { |
| 437 | 0 | if (path in this.paths) return 'real'; |
| 438 | 0 | if (path in this.virtuals) return 'virtual'; |
| 439 | 0 | if (path in this.nested) return 'nested'; |
| 440 | 0 | if (path in this.subpaths) return 'real'; |
| 441 | ||
| 442 | 0 | if (/\.\d+\.|\.\d+$/.test(path) && getPositionalPath(this, path)) { |
| 443 | 0 | return 'real'; |
| 444 | } else { | |
| 445 | 0 | return 'adhocOrUndefined' |
| 446 | } | |
| 447 | }; | |
| 448 | ||
| 449 | /*! | |
| 450 | * ignore | |
| 451 | */ | |
| 452 | ||
| 453 | 1 | function getPositionalPath (self, path) { |
| 454 | 0 | var subpaths = path.split(/\.(\d+)\.|\.(\d+)$/).filter(Boolean); |
| 455 | 0 | if (subpaths.length < 2) { |
| 456 | 0 | return self.paths[subpaths[0]]; |
| 457 | } | |
| 458 | ||
| 459 | 0 | var val = self.path(subpaths[0]); |
| 460 | 0 | if (!val) return val; |
| 461 | ||
| 462 | 0 | var last = subpaths.length - 1 |
| 463 | , subpath | |
| 464 | , i = 1; | |
| 465 | ||
| 466 | 0 | for (; i < subpaths.length; ++i) { |
| 467 | 0 | subpath = subpaths[i]; |
| 468 | ||
| 469 | 0 | if (i === last && val && !val.schema && !/\D/.test(subpath)) { |
| 470 | 0 | if (val instanceof Types.Array) { |
| 471 | // StringSchema, NumberSchema, etc | |
| 472 | 0 | val = val.caster; |
| 473 | } else { | |
| 474 | 0 | val = undefined; |
| 475 | } | |
| 476 | 0 | break; |
| 477 | } | |
| 478 | ||
| 479 | // ignore if its just a position segment: path.0.subpath | |
| 480 | 0 | if (!/\D/.test(subpath)) continue; |
| 481 | ||
| 482 | 0 | if (!(val && val.schema)) { |
| 483 | 0 | val = undefined; |
| 484 | 0 | break; |
| 485 | } | |
| 486 | ||
| 487 | 0 | val = val.schema.path(subpath); |
| 488 | } | |
| 489 | ||
| 490 | 0 | return self.subpaths[path] = val; |
| 491 | } | |
| 492 | ||
| 493 | /** | |
| 494 | * Adds a method call to the queue. | |
| 495 | * | |
| 496 | * @param {String} name name of the document method to call later | |
| 497 | * @param {Array} args arguments to pass to the method | |
| 498 | * @api private | |
| 499 | */ | |
| 500 | ||
| 501 | 1 | Schema.prototype.queue = function(name, args){ |
| 502 | 0 | this.callQueue.push([name, args]); |
| 503 | 0 | return this; |
| 504 | }; | |
| 505 | ||
| 506 | /** | |
| 507 | * Defines a pre hook for the document. | |
| 508 | * | |
| 509 | * ####Example | |
| 510 | * | |
| 511 | * var toySchema = new Schema(..); | |
| 512 | * | |
| 513 | * toySchema.pre('save', function (next) { | |
| 514 | * if (!this.created) this.created = new Date; | |
| 515 | * next(); | |
| 516 | * }) | |
| 517 | * | |
| 518 | * toySchema.pre('validate', function (next) { | |
| 519 | * if (this.name != 'Woody') this.name = 'Woody'; | |
| 520 | * next(); | |
| 521 | * }) | |
| 522 | * | |
| 523 | * @param {String} method | |
| 524 | * @param {Function} callback | |
| 525 | * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3 | |
| 526 | * @api public | |
| 527 | */ | |
| 528 | ||
| 529 | 1 | Schema.prototype.pre = function(){ |
| 530 | 0 | return this.queue('pre', arguments); |
| 531 | }; | |
| 532 | ||
| 533 | /** | |
| 534 | * Defines a post hook for the document | |
| 535 | * | |
| 536 | * Post hooks fire `on` the event emitted from document instances of Models compiled from this schema. | |
| 537 | * | |
| 538 | * var schema = new Schema(..); | |
| 539 | * schema.post('save', function (doc) { | |
| 540 | * console.log('this fired after a document was saved'); | |
| 541 | * }); | |
| 542 | * | |
| 543 | * var Model = mongoose.model('Model', schema); | |
| 544 | * | |
| 545 | * var m = new Model(..); | |
| 546 | * m.save(function (err) { | |
| 547 | * console.log('this fires after the `post` hook'); | |
| 548 | * }); | |
| 549 | * | |
| 550 | * @param {String} method name of the method to hook | |
| 551 | * @param {Function} fn callback | |
| 552 | * @see hooks.js https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3 | |
| 553 | * @api public | |
| 554 | */ | |
| 555 | ||
| 556 | 1 | Schema.prototype.post = function(method, fn){ |
| 557 | 0 | return this.queue('on', arguments); |
| 558 | }; | |
| 559 | ||
| 560 | /** | |
| 561 | * Registers a plugin for this schema. | |
| 562 | * | |
| 563 | * @param {Function} plugin callback | |
| 564 | * @param {Object} opts | |
| 565 | * @see plugins | |
| 566 | * @api public | |
| 567 | */ | |
| 568 | ||
| 569 | 1 | Schema.prototype.plugin = function (fn, opts) { |
| 570 | 0 | fn(this, opts); |
| 571 | 0 | return this; |
| 572 | }; | |
| 573 | ||
| 574 | /** | |
| 575 | * Adds an instance method to documents constructed from Models compiled from this schema. | |
| 576 | * | |
| 577 | * ####Example | |
| 578 | * | |
| 579 | * var schema = kittySchema = new Schema(..); | |
| 580 | * | |
| 581 | * schema.method('meow', function () { | |
| 582 | * console.log('meeeeeoooooooooooow'); | |
| 583 | * }) | |
| 584 | * | |
| 585 | * var Kitty = mongoose.model('Kitty', schema); | |
| 586 | * | |
| 587 | * var fizz = new Kitty; | |
| 588 | * fizz.meow(); // meeeeeooooooooooooow | |
| 589 | * | |
| 590 | * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as methods. | |
| 591 | * | |
| 592 | * schema.method({ | |
| 593 | * purr: function () {} | |
| 594 | * , scratch: function () {} | |
| 595 | * }); | |
| 596 | * | |
| 597 | * // later | |
| 598 | * fizz.purr(); | |
| 599 | * fizz.scratch(); | |
| 600 | * | |
| 601 | * @param {String|Object} method name | |
| 602 | * @param {Function} [fn] | |
| 603 | * @api public | |
| 604 | */ | |
| 605 | ||
| 606 | 1 | Schema.prototype.method = function (name, fn) { |
| 607 | 0 | if ('string' != typeof name) |
| 608 | 0 | for (var i in name) |
| 609 | 0 | this.methods[i] = name[i]; |
| 610 | else | |
| 611 | 0 | this.methods[name] = fn; |
| 612 | 0 | return this; |
| 613 | }; | |
| 614 | ||
| 615 | /** | |
| 616 | * Adds static "class" methods to Models compiled from this schema. | |
| 617 | * | |
| 618 | * ####Example | |
| 619 | * | |
| 620 | * var schema = new Schema(..); | |
| 621 | * schema.static('findByName', function (name, callback) { | |
| 622 | * return this.find({ name: name }, callback); | |
| 623 | * }); | |
| 624 | * | |
| 625 | * var Drink = mongoose.model('Drink', schema); | |
| 626 | * Drink.findByName('sanpellegrino', function (err, drinks) { | |
| 627 | * // | |
| 628 | * }); | |
| 629 | * | |
| 630 | * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics. | |
| 631 | * | |
| 632 | * @param {String} name | |
| 633 | * @param {Function} fn | |
| 634 | * @api public | |
| 635 | */ | |
| 636 | ||
| 637 | 1 | Schema.prototype.static = function(name, fn) { |
| 638 | 0 | if ('string' != typeof name) |
| 639 | 0 | for (var i in name) |
| 640 | 0 | this.statics[i] = name[i]; |
| 641 | else | |
| 642 | 0 | this.statics[name] = fn; |
| 643 | 0 | return this; |
| 644 | }; | |
| 645 | ||
| 646 | /** | |
| 647 | * Defines an index (most likely compound) for this schema. | |
| 648 | * | |
| 649 | * ####Example | |
| 650 | * | |
| 651 | * schema.index({ first: 1, last: -1 }) | |
| 652 | * | |
| 653 | * @param {Object} fields | |
| 654 | * @param {Object} [options] | |
| 655 | * @api public | |
| 656 | */ | |
| 657 | ||
| 658 | 1 | Schema.prototype.index = function (fields, options) { |
| 659 | 0 | options || (options = {}); |
| 660 | ||
| 661 | 0 | if (options.expires) |
| 662 | 0 | utils.expires(options); |
| 663 | ||
| 664 | 0 | this._indexes.push([fields, options]); |
| 665 | 0 | return this; |
| 666 | }; | |
| 667 | ||
| 668 | /** | |
| 669 | * Sets/gets a schema option. | |
| 670 | * | |
| 671 | * @param {String} key option name | |
| 672 | * @param {Object} [value] if not passed, the current option value is returned | |
| 673 | * @api public | |
| 674 | */ | |
| 675 | ||
| 676 | 1 | Schema.prototype.set = function (key, value, _tags) { |
| 677 | 0 | if (1 === arguments.length) { |
| 678 | 0 | return this.options[key]; |
| 679 | } | |
| 680 | ||
| 681 | 0 | switch (key) { |
| 682 | case 'read': | |
| 683 | 0 | this.options[key] = utils.readPref(value, _tags) |
| 684 | 0 | break; |
| 685 | case 'safe': | |
| 686 | 0 | this.options[key] = false === value |
| 687 | ? { w: 0 } | |
| 688 | : value | |
| 689 | 0 | break; |
| 690 | default: | |
| 691 | 0 | this.options[key] = value; |
| 692 | } | |
| 693 | ||
| 694 | 0 | return this; |
| 695 | } | |
| 696 | ||
| 697 | /** | |
| 698 | * Gets a schema option. | |
| 699 | * | |
| 700 | * @param {String} key option name | |
| 701 | * @api public | |
| 702 | */ | |
| 703 | ||
| 704 | 1 | Schema.prototype.get = function (key) { |
| 705 | 0 | return this.options[key]; |
| 706 | } | |
| 707 | ||
| 708 | /** | |
| 709 | * The allowed index types | |
| 710 | * | |
| 711 | * @static indexTypes | |
| 712 | * @receiver Schema | |
| 713 | * @api public | |
| 714 | */ | |
| 715 | ||
| 716 | 1 | var indexTypes = '2d 2dsphere hashed text'.split(' '); |
| 717 | ||
| 718 | 1 | Object.defineProperty(Schema, 'indexTypes', { |
| 719 | 0 | get: function () { return indexTypes } |
| 720 | 0 | , set: function () { throw new Error('Cannot overwrite Schema.indexTypes') } |
| 721 | }) | |
| 722 | ||
| 723 | /** | |
| 724 | * Compiles indexes from fields and schema-level indexes | |
| 725 | * | |
| 726 | * @api public | |
| 727 | */ | |
| 728 | ||
| 729 | 1 | Schema.prototype.indexes = function () { |
| 730 | 0 | 'use strict'; |
| 731 | ||
| 732 | 0 | var indexes = [] |
| 733 | , seenSchemas = [] | |
| 734 | 0 | collectIndexes(this); |
| 735 | 0 | return indexes; |
| 736 | ||
| 737 | 0 | function collectIndexes (schema, prefix) { |
| 738 | 0 | if (~seenSchemas.indexOf(schema)) return; |
| 739 | 0 | seenSchemas.push(schema); |
| 740 | ||
| 741 | 0 | prefix = prefix || ''; |
| 742 | ||
| 743 | 0 | var key, path, index, field, isObject, options, type; |
| 744 | 0 | var keys = Object.keys(schema.paths); |
| 745 | ||
| 746 | 0 | for (var i = 0; i < keys.length; ++i) { |
| 747 | 0 | key = keys[i]; |
| 748 | 0 | path = schema.paths[key]; |
| 749 | ||
| 750 | 0 | if (path instanceof Types.DocumentArray) { |
| 751 | 0 | collectIndexes(path.schema, key + '.'); |
| 752 | } else { | |
| 753 | 0 | index = path._index; |
| 754 | ||
| 755 | 0 | if (false !== index && null != index) { |
| 756 | 0 | field = {}; |
| 757 | 0 | isObject = utils.isObject(index); |
| 758 | 0 | options = isObject ? index : {}; |
| 759 | 0 | type = 'string' == typeof index ? index : |
| 760 | isObject ? index.type : | |
| 761 | false; | |
| 762 | ||
| 763 | 0 | if (type && ~Schema.indexTypes.indexOf(type)) { |
| 764 | 0 | field[prefix + key] = type; |
| 765 | } else { | |
| 766 | 0 | field[prefix + key] = 1; |
| 767 | } | |
| 768 | ||
| 769 | 0 | delete options.type; |
| 770 | 0 | if (!('background' in options)) { |
| 771 | 0 | options.background = true; |
| 772 | } | |
| 773 | ||
| 774 | 0 | indexes.push([field, options]); |
| 775 | } | |
| 776 | } | |
| 777 | } | |
| 778 | ||
| 779 | 0 | if (prefix) { |
| 780 | 0 | fixSubIndexPaths(schema, prefix); |
| 781 | } else { | |
| 782 | 0 | schema._indexes.forEach(function (index) { |
| 783 | 0 | if (!('background' in index[1])) index[1].background = true; |
| 784 | }); | |
| 785 | 0 | indexes = indexes.concat(schema._indexes); |
| 786 | } | |
| 787 | } | |
| 788 | ||
| 789 | /*! | |
| 790 | * Checks for indexes added to subdocs using Schema.index(). | |
| 791 | * These indexes need their paths prefixed properly. | |
| 792 | * | |
| 793 | * schema._indexes = [ [indexObj, options], [indexObj, options] ..] | |
| 794 | */ | |
| 795 | ||
| 796 | 0 | function fixSubIndexPaths (schema, prefix) { |
| 797 | 0 | var subindexes = schema._indexes |
| 798 | , len = subindexes.length | |
| 799 | , indexObj | |
| 800 | , newindex | |
| 801 | , klen | |
| 802 | , keys | |
| 803 | , key | |
| 804 | , i = 0 | |
| 805 | , j | |
| 806 | ||
| 807 | 0 | for (i = 0; i < len; ++i) { |
| 808 | 0 | indexObj = subindexes[i][0]; |
| 809 | 0 | keys = Object.keys(indexObj); |
| 810 | 0 | klen = keys.length; |
| 811 | 0 | newindex = {}; |
| 812 | ||
| 813 | // use forward iteration, order matters | |
| 814 | 0 | for (j = 0; j < klen; ++j) { |
| 815 | 0 | key = keys[j]; |
| 816 | 0 | newindex[prefix + key] = indexObj[key]; |
| 817 | } | |
| 818 | ||
| 819 | 0 | indexes.push([newindex, subindexes[i][1]]); |
| 820 | } | |
| 821 | } | |
| 822 | } | |
| 823 | ||
| 824 | /** | |
| 825 | * Creates a virtual type with the given name. | |
| 826 | * | |
| 827 | * @param {String} name | |
| 828 | * @param {Object} [options] | |
| 829 | * @return {VirtualType} | |
| 830 | */ | |
| 831 | ||
| 832 | 1 | Schema.prototype.virtual = function (name, options) { |
| 833 | 0 | var virtuals = this.virtuals; |
| 834 | 0 | var parts = name.split('.'); |
| 835 | 0 | return virtuals[name] = parts.reduce(function (mem, part, i) { |
| 836 | 0 | mem[part] || (mem[part] = (i === parts.length-1) |
| 837 | ? new VirtualType(options, name) | |
| 838 | : {}); | |
| 839 | 0 | return mem[part]; |
| 840 | }, this.tree); | |
| 841 | }; | |
| 842 | ||
| 843 | /** | |
| 844 | * Returns the virtual type with the given `name`. | |
| 845 | * | |
| 846 | * @param {String} name | |
| 847 | * @return {VirtualType} | |
| 848 | */ | |
| 849 | ||
| 850 | 1 | Schema.prototype.virtualpath = function (name) { |
| 851 | 0 | return this.virtuals[name]; |
| 852 | }; | |
| 853 | ||
| 854 | /*! | |
| 855 | * Module exports. | |
| 856 | */ | |
| 857 | ||
| 858 | 1 | module.exports = exports = Schema; |
| 859 | ||
| 860 | // require down here because of reference issues | |
| 861 | ||
| 862 | /** | |
| 863 | * The various built-in Mongoose Schema Types. | |
| 864 | * | |
| 865 | * ####Example: | |
| 866 | * | |
| 867 | * var mongoose = require('mongoose'); | |
| 868 | * var ObjectId = mongoose.Schema.Types.ObjectId; | |
| 869 | * | |
| 870 | * ####Types: | |
| 871 | * | |
| 872 | * - [String](#schema-string-js) | |
| 873 | * - [Number](#schema-number-js) | |
| 874 | * - [Boolean](#schema-boolean-js) | Bool | |
| 875 | * - [Array](#schema-array-js) | |
| 876 | * - [Buffer](#schema-buffer-js) | |
| 877 | * - [Date](#schema-date-js) | |
| 878 | * - [ObjectId](#schema-objectid-js) | Oid | |
| 879 | * - [Mixed](#schema-mixed-js) | |
| 880 | * | |
| 881 | * Using this exposed access to the `Mixed` SchemaType, we can use them in our schema. | |
| 882 | * | |
| 883 | * var Mixed = mongoose.Schema.Types.Mixed; | |
| 884 | * new mongoose.Schema({ _user: Mixed }) | |
| 885 | * | |
| 886 | * @api public | |
| 887 | */ | |
| 888 | ||
| 889 | 1 | Schema.Types = require('./schema/index'); |
| 890 | ||
| 891 | /*! | |
| 892 | * ignore | |
| 893 | */ | |
| 894 | ||
| 895 | 1 | Types = Schema.Types; |
| 896 | 1 | Query = require('./query'); |
| 897 | 1 | var ObjectId = exports.ObjectId = Types.ObjectId; |
| 898 | ||
| 899 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype') |
| 6 | , CastError = SchemaType.CastError | |
| 7 | , NumberSchema = require('./number') | |
| 8 | , Types = { | |
| 9 | Boolean: require('./boolean') | |
| 10 | , Date: require('./date') | |
| 11 | , Number: require('./number') | |
| 12 | , String: require('./string') | |
| 13 | , ObjectId: require('./objectid') | |
| 14 | , Buffer: require('./buffer') | |
| 15 | } | |
| 16 | , MongooseArray = require('../types').Array | |
| 17 | , EmbeddedDoc = require('../types').Embedded | |
| 18 | , Mixed = require('./mixed') | |
| 19 | , Query = require('../query') | |
| 20 | , utils = require('../utils') | |
| 21 | , isMongooseObject = utils.isMongooseObject | |
| 22 | ||
| 23 | /** | |
| 24 | * Array SchemaType constructor | |
| 25 | * | |
| 26 | * @param {String} key | |
| 27 | * @param {SchemaType} cast | |
| 28 | * @param {Object} options | |
| 29 | * @inherits SchemaType | |
| 30 | * @api private | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | function SchemaArray (key, cast, options) { |
| 34 | 0 | if (cast) { |
| 35 | 0 | var castOptions = {}; |
| 36 | ||
| 37 | 0 | if ('Object' === cast.constructor.name) { |
| 38 | 0 | if (cast.type) { |
| 39 | // support { type: Woot } | |
| 40 | 0 | castOptions = utils.clone(cast); // do not alter user arguments |
| 41 | 0 | delete castOptions.type; |
| 42 | 0 | cast = cast.type; |
| 43 | } else { | |
| 44 | 0 | cast = Mixed; |
| 45 | } | |
| 46 | } | |
| 47 | ||
| 48 | // support { type: 'String' } | |
| 49 | 0 | var name = 'string' == typeof cast |
| 50 | ? cast | |
| 51 | : cast.name; | |
| 52 | ||
| 53 | 0 | var caster = name in Types |
| 54 | ? Types[name] | |
| 55 | : cast; | |
| 56 | ||
| 57 | 0 | this.casterConstructor = caster; |
| 58 | 0 | this.caster = new caster(null, castOptions); |
| 59 | 0 | if (!(this.caster instanceof EmbeddedDoc)) { |
| 60 | 0 | this.caster.path = key; |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | 0 | SchemaType.call(this, key, options); |
| 65 | ||
| 66 | 0 | var self = this |
| 67 | , defaultArr | |
| 68 | , fn; | |
| 69 | ||
| 70 | 0 | if (this.defaultValue) { |
| 71 | 0 | defaultArr = this.defaultValue; |
| 72 | 0 | fn = 'function' == typeof defaultArr; |
| 73 | } | |
| 74 | ||
| 75 | 0 | this.default(function(){ |
| 76 | 0 | var arr = fn ? defaultArr() : defaultArr || []; |
| 77 | 0 | return new MongooseArray(arr, self.path, this); |
| 78 | }); | |
| 79 | }; | |
| 80 | ||
| 81 | /*! | |
| 82 | * Inherits from SchemaType. | |
| 83 | */ | |
| 84 | ||
| 85 | 1 | SchemaArray.prototype.__proto__ = SchemaType.prototype; |
| 86 | ||
| 87 | /** | |
| 88 | * Check required | |
| 89 | * | |
| 90 | * @param {Array} value | |
| 91 | * @api private | |
| 92 | */ | |
| 93 | ||
| 94 | 1 | SchemaArray.prototype.checkRequired = function (value) { |
| 95 | 0 | return !!(value && value.length); |
| 96 | }; | |
| 97 | ||
| 98 | /** | |
| 99 | * Overrides the getters application for the population special-case | |
| 100 | * | |
| 101 | * @param {Object} value | |
| 102 | * @param {Object} scope | |
| 103 | * @api private | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | SchemaArray.prototype.applyGetters = function (value, scope) { |
| 107 | 0 | if (this.caster.options && this.caster.options.ref) { |
| 108 | // means the object id was populated | |
| 109 | 0 | return value; |
| 110 | } | |
| 111 | ||
| 112 | 0 | return SchemaType.prototype.applyGetters.call(this, value, scope); |
| 113 | }; | |
| 114 | ||
| 115 | /** | |
| 116 | * Casts values for set(). | |
| 117 | * | |
| 118 | * @param {Object} value | |
| 119 | * @param {Document} doc document that triggers the casting | |
| 120 | * @param {Boolean} init whether this is an initialization cast | |
| 121 | * @api private | |
| 122 | */ | |
| 123 | ||
| 124 | 1 | SchemaArray.prototype.cast = function (value, doc, init) { |
| 125 | 0 | if (Array.isArray(value)) { |
| 126 | ||
| 127 | 0 | if (!value.length && doc) { |
| 128 | 0 | var indexes = doc.schema.indexedPaths(); |
| 129 | ||
| 130 | 0 | for (var i = 0, l = indexes.length; i < l; ++i) { |
| 131 | 0 | var pathIndex = indexes[i][0][this.path]; |
| 132 | 0 | if ('2dsphere' === pathIndex || '2d' === pathIndex) { |
| 133 | 0 | return; |
| 134 | } | |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | 0 | if (!(value instanceof MongooseArray)) { |
| 139 | 0 | value = new MongooseArray(value, this.path, doc); |
| 140 | } | |
| 141 | ||
| 142 | 0 | if (this.caster) { |
| 143 | 0 | try { |
| 144 | 0 | for (var i = 0, l = value.length; i < l; i++) { |
| 145 | 0 | value[i] = this.caster.cast(value[i], doc, init); |
| 146 | } | |
| 147 | } catch (e) { | |
| 148 | // rethrow | |
| 149 | 0 | throw new CastError(e.type, value, this.path); |
| 150 | } | |
| 151 | } | |
| 152 | ||
| 153 | 0 | return value; |
| 154 | } else { | |
| 155 | 0 | return this.cast([value], doc, init); |
| 156 | } | |
| 157 | }; | |
| 158 | ||
| 159 | /** | |
| 160 | * Casts values for queries. | |
| 161 | * | |
| 162 | * @param {String} $conditional | |
| 163 | * @param {any} [value] | |
| 164 | * @api private | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | SchemaArray.prototype.castForQuery = function ($conditional, value) { |
| 168 | 0 | var handler |
| 169 | , val; | |
| 170 | ||
| 171 | 0 | if (arguments.length === 2) { |
| 172 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 173 | ||
| 174 | 0 | if (!handler) { |
| 175 | 0 | throw new Error("Can't use " + $conditional + " with Array."); |
| 176 | } | |
| 177 | ||
| 178 | 0 | val = handler.call(this, value); |
| 179 | ||
| 180 | } else { | |
| 181 | ||
| 182 | 0 | val = $conditional; |
| 183 | 0 | var proto = this.casterConstructor.prototype; |
| 184 | 0 | var method = proto.castForQuery || proto.cast; |
| 185 | 0 | var caster = this.caster; |
| 186 | ||
| 187 | 0 | if (Array.isArray(val)) { |
| 188 | 0 | val = val.map(function (v) { |
| 189 | 0 | if (method) v = method.call(caster, v); |
| 190 | 0 | return isMongooseObject(v) |
| 191 | ? v.toObject() | |
| 192 | : v; | |
| 193 | }); | |
| 194 | ||
| 195 | 0 | } else if (method) { |
| 196 | 0 | val = method.call(caster, val); |
| 197 | } | |
| 198 | } | |
| 199 | ||
| 200 | 0 | return val && isMongooseObject(val) |
| 201 | ? val.toObject() | |
| 202 | : val; | |
| 203 | }; | |
| 204 | ||
| 205 | /*! | |
| 206 | * @ignore | |
| 207 | * | |
| 208 | * $atomic cast helpers | |
| 209 | */ | |
| 210 | ||
| 211 | 1 | function castToNumber (val) { |
| 212 | 0 | return Types.Number.prototype.cast.call(this, val); |
| 213 | } | |
| 214 | ||
| 215 | 1 | function castArraysOfNumbers (arr, self) { |
| 216 | 0 | self || (self = this); |
| 217 | ||
| 218 | 0 | arr.forEach(function (v, i) { |
| 219 | 0 | if (Array.isArray(v)) { |
| 220 | 0 | castArraysOfNumbers(v, self); |
| 221 | } else { | |
| 222 | 0 | arr[i] = castToNumber.call(self, v); |
| 223 | } | |
| 224 | }); | |
| 225 | } | |
| 226 | ||
| 227 | 1 | function cast$near (val) { |
| 228 | 0 | if (Array.isArray(val)) { |
| 229 | 0 | castArraysOfNumbers(val, this); |
| 230 | 0 | return val; |
| 231 | } | |
| 232 | ||
| 233 | 0 | if (val && val.$geometry) { |
| 234 | 0 | return cast$geometry(val, this); |
| 235 | } | |
| 236 | ||
| 237 | 0 | return SchemaArray.prototype.castForQuery.call(this, val); |
| 238 | } | |
| 239 | ||
| 240 | 1 | function cast$geometry (val, self) { |
| 241 | 0 | switch (val.$geometry.type) { |
| 242 | case 'Polygon': | |
| 243 | case 'LineString': | |
| 244 | case 'Point': | |
| 245 | 0 | castArraysOfNumbers(val.$geometry.coordinates, self); |
| 246 | 0 | break; |
| 247 | default: | |
| 248 | // ignore unknowns | |
| 249 | 0 | break; |
| 250 | } | |
| 251 | ||
| 252 | 0 | if (val.$maxDistance) { |
| 253 | 0 | val.$maxDistance = castToNumber.call(self, val.$maxDistance); |
| 254 | } | |
| 255 | ||
| 256 | 0 | return val; |
| 257 | } | |
| 258 | ||
| 259 | 1 | function cast$within (val) { |
| 260 | 0 | var self = this; |
| 261 | ||
| 262 | 0 | if (val.$maxDistance) { |
| 263 | 0 | val.$maxDistance = castToNumber.call(self, val.$maxDistance); |
| 264 | } | |
| 265 | ||
| 266 | 0 | if (val.$box || val.$polygon) { |
| 267 | 0 | var type = val.$box ? '$box' : '$polygon'; |
| 268 | 0 | val[type].forEach(function (arr) { |
| 269 | 0 | if (!Array.isArray(arr)) { |
| 270 | 0 | var msg = 'Invalid $within $box argument. ' |
| 271 | + 'Expected an array, received ' + arr; | |
| 272 | 0 | throw new TypeError(msg); |
| 273 | } | |
| 274 | 0 | arr.forEach(function (v, i) { |
| 275 | 0 | arr[i] = castToNumber.call(this, v); |
| 276 | }); | |
| 277 | }) | |
| 278 | 0 | } else if (val.$center || val.$centerSphere) { |
| 279 | 0 | var type = val.$center ? '$center' : '$centerSphere'; |
| 280 | 0 | val[type].forEach(function (item, i) { |
| 281 | 0 | if (Array.isArray(item)) { |
| 282 | 0 | item.forEach(function (v, j) { |
| 283 | 0 | item[j] = castToNumber.call(this, v); |
| 284 | }); | |
| 285 | } else { | |
| 286 | 0 | val[type][i] = castToNumber.call(this, item); |
| 287 | } | |
| 288 | }) | |
| 289 | 0 | } else if (val.$geometry) { |
| 290 | 0 | cast$geometry(val, this); |
| 291 | } | |
| 292 | ||
| 293 | 0 | return val; |
| 294 | } | |
| 295 | ||
| 296 | 1 | function cast$all (val) { |
| 297 | 0 | if (!Array.isArray(val)) { |
| 298 | 0 | val = [val]; |
| 299 | } | |
| 300 | ||
| 301 | 0 | val = val.map(function (v) { |
| 302 | 0 | if (utils.isObject(v)) { |
| 303 | 0 | var o = {}; |
| 304 | 0 | o[this.path] = v; |
| 305 | 0 | var query = new Query(o); |
| 306 | 0 | query.cast(this.casterConstructor); |
| 307 | 0 | return query._conditions[this.path]; |
| 308 | } | |
| 309 | 0 | return v; |
| 310 | }, this); | |
| 311 | ||
| 312 | 0 | return this.castForQuery(val); |
| 313 | } | |
| 314 | ||
| 315 | 1 | function cast$elemMatch (val) { |
| 316 | 0 | if (val.$in) { |
| 317 | 0 | val.$in = this.castForQuery('$in', val.$in); |
| 318 | 0 | return val; |
| 319 | } | |
| 320 | ||
| 321 | 0 | var query = new Query(val); |
| 322 | 0 | query.cast(this.casterConstructor); |
| 323 | 0 | return query._conditions; |
| 324 | } | |
| 325 | ||
| 326 | 1 | function cast$geoIntersects (val) { |
| 327 | 0 | var geo = val.$geometry; |
| 328 | 0 | if (!geo) return; |
| 329 | ||
| 330 | 0 | cast$geometry(val, this); |
| 331 | 0 | return val; |
| 332 | } | |
| 333 | ||
| 334 | 1 | var handle = SchemaArray.prototype.$conditionalHandlers = {}; |
| 335 | ||
| 336 | 1 | handle.$all = cast$all; |
| 337 | 1 | handle.$options = String; |
| 338 | 1 | handle.$elemMatch = cast$elemMatch; |
| 339 | 1 | handle.$geoIntersects = cast$geoIntersects; |
| 340 | ||
| 341 | 1 | handle.$near = |
| 342 | handle.$nearSphere = cast$near; | |
| 343 | ||
| 344 | 1 | handle.$within = |
| 345 | handle.$geoWithin = cast$within; | |
| 346 | ||
| 347 | 1 | handle.$size = |
| 348 | handle.$maxDistance = castToNumber; | |
| 349 | ||
| 350 | 1 | handle.$regex = |
| 351 | handle.$ne = | |
| 352 | handle.$in = | |
| 353 | handle.$nin = | |
| 354 | handle.$gt = | |
| 355 | handle.$gte = | |
| 356 | handle.$lt = | |
| 357 | handle.$lte = SchemaArray.prototype.castForQuery; | |
| 358 | ||
| 359 | /*! | |
| 360 | * Module exports. | |
| 361 | */ | |
| 362 | ||
| 363 | 1 | module.exports = SchemaArray; |
| 364 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype'); |
| 6 | ||
| 7 | /** | |
| 8 | * Boolean SchemaType constructor. | |
| 9 | * | |
| 10 | * @param {String} path | |
| 11 | * @param {Object} options | |
| 12 | * @inherits SchemaType | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | function SchemaBoolean (path, options) { |
| 17 | 2 | SchemaType.call(this, path, options); |
| 18 | }; | |
| 19 | ||
| 20 | /*! | |
| 21 | * Inherits from SchemaType. | |
| 22 | */ | |
| 23 | 1 | SchemaBoolean.prototype.__proto__ = SchemaType.prototype; |
| 24 | ||
| 25 | /** | |
| 26 | * Required validator | |
| 27 | * | |
| 28 | * @api private | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | SchemaBoolean.prototype.checkRequired = function (value) { |
| 32 | 0 | return value === true || value === false; |
| 33 | }; | |
| 34 | ||
| 35 | /** | |
| 36 | * Casts to boolean | |
| 37 | * | |
| 38 | * @param {Object} value | |
| 39 | * @api private | |
| 40 | */ | |
| 41 | ||
| 42 | 1 | SchemaBoolean.prototype.cast = function (value) { |
| 43 | 0 | if (null === value) return value; |
| 44 | 0 | if ('0' === value) return false; |
| 45 | 0 | if ('true' === value) return true; |
| 46 | 0 | if ('false' === value) return false; |
| 47 | 0 | return !! value; |
| 48 | } | |
| 49 | ||
| 50 | /*! | |
| 51 | * ignore | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | function handleArray (val) { |
| 55 | 0 | var self = this; |
| 56 | 0 | return val.map(function (m) { |
| 57 | 0 | return self.cast(m); |
| 58 | }); | |
| 59 | } | |
| 60 | ||
| 61 | 1 | SchemaBoolean.$conditionalHandlers = { |
| 62 | '$in': handleArray | |
| 63 | } | |
| 64 | ||
| 65 | /** | |
| 66 | * Casts contents for queries. | |
| 67 | * | |
| 68 | * @param {String} $conditional | |
| 69 | * @param {any} val | |
| 70 | * @api private | |
| 71 | */ | |
| 72 | ||
| 73 | 1 | SchemaBoolean.prototype.castForQuery = function ($conditional, val) { |
| 74 | 0 | var handler; |
| 75 | 0 | if (2 === arguments.length) { |
| 76 | 0 | handler = SchemaBoolean.$conditionalHandlers[$conditional]; |
| 77 | ||
| 78 | 0 | if (handler) { |
| 79 | 0 | return handler.call(this, val); |
| 80 | } | |
| 81 | ||
| 82 | 0 | return this.cast(val); |
| 83 | } | |
| 84 | ||
| 85 | 0 | return this.cast($conditional); |
| 86 | }; | |
| 87 | ||
| 88 | /*! | |
| 89 | * Module exports. | |
| 90 | */ | |
| 91 | ||
| 92 | 1 | module.exports = SchemaBoolean; |
| 93 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype') |
| 6 | , CastError = SchemaType.CastError | |
| 7 | , MongooseBuffer = require('../types').Buffer | |
| 8 | , Binary = MongooseBuffer.Binary | |
| 9 | , Query = require('../query') | |
| 10 | , utils = require('../utils') | |
| 11 | , Document | |
| 12 | ||
| 13 | /** | |
| 14 | * Buffer SchemaType constructor | |
| 15 | * | |
| 16 | * @param {String} key | |
| 17 | * @param {SchemaType} cast | |
| 18 | * @inherits SchemaType | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function SchemaBuffer (key, options) { |
| 23 | 0 | SchemaType.call(this, key, options, 'Buffer'); |
| 24 | }; | |
| 25 | ||
| 26 | /*! | |
| 27 | * Inherits from SchemaType. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | SchemaBuffer.prototype.__proto__ = SchemaType.prototype; |
| 31 | ||
| 32 | /** | |
| 33 | * Check required | |
| 34 | * | |
| 35 | * @api private | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | SchemaBuffer.prototype.checkRequired = function (value, doc) { |
| 39 | 0 | if (SchemaType._isRef(this, value, doc, true)) { |
| 40 | 0 | return null != value; |
| 41 | } else { | |
| 42 | 0 | return !!(value && value.length); |
| 43 | } | |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Casts contents | |
| 48 | * | |
| 49 | * @param {Object} value | |
| 50 | * @param {Document} doc document that triggers the casting | |
| 51 | * @param {Boolean} init | |
| 52 | * @api private | |
| 53 | */ | |
| 54 | ||
| 55 | 1 | SchemaBuffer.prototype.cast = function (value, doc, init) { |
| 56 | 0 | if (SchemaType._isRef(this, value, doc, init)) { |
| 57 | // wait! we may need to cast this to a document | |
| 58 | ||
| 59 | 0 | if (null == value) { |
| 60 | 0 | return value; |
| 61 | } | |
| 62 | ||
| 63 | // lazy load | |
| 64 | 0 | Document || (Document = require('./../document')); |
| 65 | ||
| 66 | 0 | if (value instanceof Document) { |
| 67 | 0 | value.$__.wasPopulated = true; |
| 68 | 0 | return value; |
| 69 | } | |
| 70 | ||
| 71 | // setting a populated path | |
| 72 | 0 | if (Buffer.isBuffer(value)) { |
| 73 | 0 | return value; |
| 74 | 0 | } else if (!utils.isObject(value)) { |
| 75 | 0 | throw new CastError('buffer', value, this.path); |
| 76 | } | |
| 77 | ||
| 78 | // Handle the case where user directly sets a populated | |
| 79 | // path to a plain object; cast to the Model used in | |
| 80 | // the population query. | |
| 81 | 0 | var path = doc.$__fullPath(this.path); |
| 82 | 0 | var owner = doc.ownerDocument ? doc.ownerDocument() : doc; |
| 83 | 0 | var pop = owner.populated(path, true); |
| 84 | 0 | var ret = new pop.options.model(value); |
| 85 | 0 | ret.$__.wasPopulated = true; |
| 86 | 0 | return ret; |
| 87 | } | |
| 88 | ||
| 89 | // documents | |
| 90 | 0 | if (value && value._id) { |
| 91 | 0 | value = value._id; |
| 92 | } | |
| 93 | ||
| 94 | 0 | if (Buffer.isBuffer(value)) { |
| 95 | 0 | if (!(value instanceof MongooseBuffer)) { |
| 96 | 0 | value = new MongooseBuffer(value, [this.path, doc]); |
| 97 | } | |
| 98 | ||
| 99 | 0 | return value; |
| 100 | 0 | } else if (value instanceof Binary) { |
| 101 | 0 | var ret = new MongooseBuffer(value.value(true), [this.path, doc]); |
| 102 | 0 | ret.subtype(value.sub_type); |
| 103 | // do not override Binary subtypes. users set this | |
| 104 | // to whatever they want. | |
| 105 | 0 | return ret; |
| 106 | } | |
| 107 | ||
| 108 | 0 | if (null === value) return value; |
| 109 | ||
| 110 | 0 | var type = typeof value; |
| 111 | 0 | if ('string' == type || 'number' == type || Array.isArray(value)) { |
| 112 | 0 | var ret = new MongooseBuffer(value, [this.path, doc]); |
| 113 | 0 | return ret; |
| 114 | } | |
| 115 | ||
| 116 | 0 | throw new CastError('buffer', value, this.path); |
| 117 | }; | |
| 118 | ||
| 119 | /*! | |
| 120 | * ignore | |
| 121 | */ | |
| 122 | 1 | function handleSingle (val) { |
| 123 | 0 | return this.castForQuery(val); |
| 124 | } | |
| 125 | ||
| 126 | 1 | function handleArray (val) { |
| 127 | 0 | var self = this; |
| 128 | 0 | return val.map( function (m) { |
| 129 | 0 | return self.castForQuery(m); |
| 130 | }); | |
| 131 | } | |
| 132 | ||
| 133 | 1 | SchemaBuffer.prototype.$conditionalHandlers = { |
| 134 | '$ne' : handleSingle | |
| 135 | , '$in' : handleArray | |
| 136 | , '$nin': handleArray | |
| 137 | , '$gt' : handleSingle | |
| 138 | , '$lt' : handleSingle | |
| 139 | , '$gte': handleSingle | |
| 140 | , '$lte': handleSingle | |
| 141 | }; | |
| 142 | ||
| 143 | /** | |
| 144 | * Casts contents for queries. | |
| 145 | * | |
| 146 | * @param {String} $conditional | |
| 147 | * @param {any} [value] | |
| 148 | * @api private | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | SchemaBuffer.prototype.castForQuery = function ($conditional, val) { |
| 152 | 0 | var handler; |
| 153 | 0 | if (arguments.length === 2) { |
| 154 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 155 | 0 | if (!handler) |
| 156 | 0 | throw new Error("Can't use " + $conditional + " with Buffer."); |
| 157 | 0 | return handler.call(this, val); |
| 158 | } else { | |
| 159 | 0 | val = $conditional; |
| 160 | 0 | return this.cast(val).toObject(); |
| 161 | } | |
| 162 | }; | |
| 163 | ||
| 164 | /*! | |
| 165 | * Module exports. | |
| 166 | */ | |
| 167 | ||
| 168 | 1 | module.exports = SchemaBuffer; |
| 169 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module requirements. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype'); |
| 6 | 1 | var CastError = SchemaType.CastError; |
| 7 | 1 | var utils = require('../utils'); |
| 8 | ||
| 9 | /** | |
| 10 | * Date SchemaType constructor. | |
| 11 | * | |
| 12 | * @param {String} key | |
| 13 | * @param {Object} options | |
| 14 | * @inherits SchemaType | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | function SchemaDate (key, options) { |
| 19 | 1 | SchemaType.call(this, key, options); |
| 20 | }; | |
| 21 | ||
| 22 | /*! | |
| 23 | * Inherits from SchemaType. | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | SchemaDate.prototype.__proto__ = SchemaType.prototype; |
| 27 | ||
| 28 | /** | |
| 29 | * Declares a TTL index (rounded to the nearest second) for _Date_ types only. | |
| 30 | * | |
| 31 | * This sets the `expiresAfterSeconds` index option available in MongoDB >= 2.1.2. | |
| 32 | * This index type is only compatible with Date types. | |
| 33 | * | |
| 34 | * ####Example: | |
| 35 | * | |
| 36 | * // expire in 24 hours | |
| 37 | * new Schema({ createdAt: { type: Date, expires: 60*60*24 }}); | |
| 38 | * | |
| 39 | * `expires` utilizes the `ms` module from [guille](https://github.com/guille/) allowing us to use a friendlier syntax: | |
| 40 | * | |
| 41 | * ####Example: | |
| 42 | * | |
| 43 | * // expire in 24 hours | |
| 44 | * new Schema({ createdAt: { type: Date, expires: '24h' }}); | |
| 45 | * | |
| 46 | * // expire in 1.5 hours | |
| 47 | * new Schema({ createdAt: { type: Date, expires: '1.5h' }}); | |
| 48 | * | |
| 49 | * // expire in 7 days | |
| 50 | * var schema = new Schema({ createdAt: Date }); | |
| 51 | * schema.path('createdAt').expires('7d'); | |
| 52 | * | |
| 53 | * @param {Number|String} when | |
| 54 | * @added 3.0.0 | |
| 55 | * @return {SchemaType} this | |
| 56 | * @api public | |
| 57 | */ | |
| 58 | ||
| 59 | 1 | SchemaDate.prototype.expires = function (when) { |
| 60 | 0 | if (!this._index || 'Object' !== this._index.constructor.name) { |
| 61 | 0 | this._index = {}; |
| 62 | } | |
| 63 | ||
| 64 | 0 | this._index.expires = when; |
| 65 | 0 | utils.expires(this._index); |
| 66 | 0 | return this; |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Required validator for date | |
| 71 | * | |
| 72 | * @api private | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | SchemaDate.prototype.checkRequired = function (value) { |
| 76 | 0 | return value instanceof Date; |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Casts to date | |
| 81 | * | |
| 82 | * @param {Object} value to cast | |
| 83 | * @api private | |
| 84 | */ | |
| 85 | ||
| 86 | 1 | SchemaDate.prototype.cast = function (value) { |
| 87 | 0 | if (value === null || value === '') |
| 88 | 0 | return null; |
| 89 | ||
| 90 | 0 | if (value instanceof Date) |
| 91 | 0 | return value; |
| 92 | ||
| 93 | 0 | var date; |
| 94 | ||
| 95 | // support for timestamps | |
| 96 | 0 | if (value instanceof Number || 'number' == typeof value |
| 97 | || String(value) == Number(value)) | |
| 98 | 0 | date = new Date(Number(value)); |
| 99 | ||
| 100 | // support for date strings | |
| 101 | 0 | else if (value.toString) |
| 102 | 0 | date = new Date(value.toString()); |
| 103 | ||
| 104 | 0 | if (date.toString() != 'Invalid Date') |
| 105 | 0 | return date; |
| 106 | ||
| 107 | 0 | throw new CastError('date', value, this.path); |
| 108 | }; | |
| 109 | ||
| 110 | /*! | |
| 111 | * Date Query casting. | |
| 112 | * | |
| 113 | * @api private | |
| 114 | */ | |
| 115 | ||
| 116 | 1 | function handleSingle (val) { |
| 117 | 0 | return this.cast(val); |
| 118 | } | |
| 119 | ||
| 120 | 1 | function handleArray (val) { |
| 121 | 0 | var self = this; |
| 122 | 0 | return val.map( function (m) { |
| 123 | 0 | return self.cast(m); |
| 124 | }); | |
| 125 | } | |
| 126 | ||
| 127 | 1 | SchemaDate.prototype.$conditionalHandlers = { |
| 128 | '$lt': handleSingle | |
| 129 | , '$lte': handleSingle | |
| 130 | , '$gt': handleSingle | |
| 131 | , '$gte': handleSingle | |
| 132 | , '$ne': handleSingle | |
| 133 | , '$in': handleArray | |
| 134 | , '$nin': handleArray | |
| 135 | , '$all': handleArray | |
| 136 | }; | |
| 137 | ||
| 138 | ||
| 139 | /** | |
| 140 | * Casts contents for queries. | |
| 141 | * | |
| 142 | * @param {String} $conditional | |
| 143 | * @param {any} [value] | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | SchemaDate.prototype.castForQuery = function ($conditional, val) { |
| 148 | 0 | var handler; |
| 149 | ||
| 150 | 0 | if (2 !== arguments.length) { |
| 151 | 0 | return this.cast($conditional); |
| 152 | } | |
| 153 | ||
| 154 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 155 | ||
| 156 | 0 | if (!handler) { |
| 157 | 0 | throw new Error("Can't use " + $conditional + " with Date."); |
| 158 | } | |
| 159 | ||
| 160 | 0 | return handler.call(this, val); |
| 161 | }; | |
| 162 | ||
| 163 | /*! | |
| 164 | * Module exports. | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | module.exports = SchemaDate; |
| 168 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var SchemaType = require('../schematype') |
| 7 | , ArrayType = require('./array') | |
| 8 | , MongooseDocumentArray = require('../types/documentarray') | |
| 9 | , Subdocument = require('../types/embedded') | |
| 10 | , Document = require('../document'); | |
| 11 | ||
| 12 | /** | |
| 13 | * SubdocsArray SchemaType constructor | |
| 14 | * | |
| 15 | * @param {String} key | |
| 16 | * @param {Schema} schema | |
| 17 | * @param {Object} options | |
| 18 | * @inherits SchemaArray | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | ||
| 22 | 1 | function DocumentArray (key, schema, options) { |
| 23 | ||
| 24 | // compile an embedded document for this schema | |
| 25 | 0 | function EmbeddedDocument () { |
| 26 | 0 | Subdocument.apply(this, arguments); |
| 27 | } | |
| 28 | ||
| 29 | 0 | EmbeddedDocument.prototype.__proto__ = Subdocument.prototype; |
| 30 | 0 | EmbeddedDocument.prototype.$__setSchema(schema); |
| 31 | 0 | EmbeddedDocument.schema = schema; |
| 32 | ||
| 33 | // apply methods | |
| 34 | 0 | for (var i in schema.methods) { |
| 35 | 0 | EmbeddedDocument.prototype[i] = schema.methods[i]; |
| 36 | } | |
| 37 | ||
| 38 | // apply statics | |
| 39 | 0 | for (var i in schema.statics) |
| 40 | 0 | EmbeddedDocument[i] = schema.statics[i]; |
| 41 | ||
| 42 | 0 | EmbeddedDocument.options = options; |
| 43 | 0 | this.schema = schema; |
| 44 | ||
| 45 | 0 | ArrayType.call(this, key, EmbeddedDocument, options); |
| 46 | ||
| 47 | 0 | this.schema = schema; |
| 48 | 0 | var path = this.path; |
| 49 | 0 | var fn = this.defaultValue; |
| 50 | ||
| 51 | 0 | this.default(function(){ |
| 52 | 0 | var arr = fn.call(this); |
| 53 | 0 | if (!Array.isArray(arr)) arr = [arr]; |
| 54 | 0 | return new MongooseDocumentArray(arr, path, this); |
| 55 | }); | |
| 56 | }; | |
| 57 | ||
| 58 | /*! | |
| 59 | * Inherits from ArrayType. | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | DocumentArray.prototype.__proto__ = ArrayType.prototype; |
| 63 | ||
| 64 | /** | |
| 65 | * Performs local validations first, then validations on each embedded doc | |
| 66 | * | |
| 67 | * @api private | |
| 68 | */ | |
| 69 | ||
| 70 | 1 | DocumentArray.prototype.doValidate = function (array, fn, scope) { |
| 71 | 0 | var self = this; |
| 72 | ||
| 73 | 0 | SchemaType.prototype.doValidate.call(this, array, function (err) { |
| 74 | 0 | if (err) return fn(err); |
| 75 | ||
| 76 | 0 | var count = array && array.length |
| 77 | , error; | |
| 78 | ||
| 79 | 0 | if (!count) return fn(); |
| 80 | ||
| 81 | // handle sparse arrays, do not use array.forEach which does not | |
| 82 | // iterate over sparse elements yet reports array.length including | |
| 83 | // them :( | |
| 84 | ||
| 85 | 0 | for (var i = 0, len = count; i < len; ++i) { |
| 86 | // sidestep sparse entries | |
| 87 | 0 | var doc = array[i]; |
| 88 | 0 | if (!doc) { |
| 89 | 0 | --count || fn(); |
| 90 | 0 | continue; |
| 91 | } | |
| 92 | ||
| 93 | 0 | ;(function (i) { |
| 94 | 0 | doc.validate(function (err) { |
| 95 | 0 | if (err && !error) { |
| 96 | // rewrite the key | |
| 97 | 0 | err.key = self.key + '.' + i + '.' + err.key; |
| 98 | 0 | return fn(error = err); |
| 99 | } | |
| 100 | 0 | --count || fn(); |
| 101 | }); | |
| 102 | })(i); | |
| 103 | } | |
| 104 | }, scope); | |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Casts contents | |
| 109 | * | |
| 110 | * @param {Object} value | |
| 111 | * @param {Document} document that triggers the casting | |
| 112 | * @api private | |
| 113 | */ | |
| 114 | ||
| 115 | 1 | DocumentArray.prototype.cast = function (value, doc, init, prev) { |
| 116 | 0 | var selected |
| 117 | , subdoc | |
| 118 | , i | |
| 119 | ||
| 120 | 0 | if (!Array.isArray(value)) { |
| 121 | 0 | return this.cast([value], doc, init, prev); |
| 122 | } | |
| 123 | ||
| 124 | 0 | if (!(value instanceof MongooseDocumentArray)) { |
| 125 | 0 | value = new MongooseDocumentArray(value, this.path, doc); |
| 126 | } | |
| 127 | ||
| 128 | 0 | i = value.length; |
| 129 | ||
| 130 | 0 | while (i--) { |
| 131 | 0 | if (!(value[i] instanceof Subdocument) && value[i]) { |
| 132 | 0 | if (init) { |
| 133 | 0 | selected || (selected = scopePaths(this, doc.$__.selected, init)); |
| 134 | 0 | subdoc = new this.casterConstructor(null, value, true, selected); |
| 135 | 0 | value[i] = subdoc.init(value[i]); |
| 136 | } else { | |
| 137 | 0 | if (prev && (subdoc = prev.id(value[i]._id))) { |
| 138 | // handle resetting doc with existing id but differing data | |
| 139 | // doc.array = [{ doc: 'val' }] | |
| 140 | 0 | subdoc.set(value[i]); |
| 141 | } else { | |
| 142 | 0 | subdoc = new this.casterConstructor(value[i], value); |
| 143 | } | |
| 144 | ||
| 145 | // if set() is hooked it will have no return value | |
| 146 | // see gh-746 | |
| 147 | 0 | value[i] = subdoc; |
| 148 | } | |
| 149 | } | |
| 150 | } | |
| 151 | ||
| 152 | 0 | return value; |
| 153 | } | |
| 154 | ||
| 155 | /*! | |
| 156 | * Scopes paths selected in a query to this array. | |
| 157 | * Necessary for proper default application of subdocument values. | |
| 158 | * | |
| 159 | * @param {DocumentArray} array - the array to scope `fields` paths | |
| 160 | * @param {Object|undefined} fields - the root fields selected in the query | |
| 161 | * @param {Boolean|undefined} init - if we are being created part of a query result | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | function scopePaths (array, fields, init) { |
| 165 | 0 | if (!(init && fields)) return undefined; |
| 166 | ||
| 167 | 0 | var path = array.path + '.' |
| 168 | , keys = Object.keys(fields) | |
| 169 | , i = keys.length | |
| 170 | , selected = {} | |
| 171 | , hasKeys | |
| 172 | , key | |
| 173 | ||
| 174 | 0 | while (i--) { |
| 175 | 0 | key = keys[i]; |
| 176 | 0 | if (0 === key.indexOf(path)) { |
| 177 | 0 | hasKeys || (hasKeys = true); |
| 178 | 0 | selected[key.substring(path.length)] = fields[key]; |
| 179 | } | |
| 180 | } | |
| 181 | ||
| 182 | 0 | return hasKeys && selected || undefined; |
| 183 | } | |
| 184 | ||
| 185 | /*! | |
| 186 | * Module exports. | |
| 187 | */ | |
| 188 | ||
| 189 | 1 | module.exports = DocumentArray; |
| 190 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module exports. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | exports.String = require('./string'); |
| 7 | ||
| 8 | 1 | exports.Number = require('./number'); |
| 9 | ||
| 10 | 1 | exports.Boolean = require('./boolean'); |
| 11 | ||
| 12 | 1 | exports.DocumentArray = require('./documentarray'); |
| 13 | ||
| 14 | 1 | exports.Array = require('./array'); |
| 15 | ||
| 16 | 1 | exports.Buffer = require('./buffer'); |
| 17 | ||
| 18 | 1 | exports.Date = require('./date'); |
| 19 | ||
| 20 | 1 | exports.ObjectId = require('./objectid'); |
| 21 | ||
| 22 | 1 | exports.Mixed = require('./mixed'); |
| 23 | ||
| 24 | // alias | |
| 25 | ||
| 26 | 1 | exports.Oid = exports.ObjectId; |
| 27 | 1 | exports.Object = exports.Mixed; |
| 28 | 1 | exports.Bool = exports.Boolean; |
| 29 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var SchemaType = require('../schematype'); |
| 7 | 1 | var utils = require('../utils'); |
| 8 | ||
| 9 | /** | |
| 10 | * Mixed SchemaType constructor. | |
| 11 | * | |
| 12 | * @param {String} path | |
| 13 | * @param {Object} options | |
| 14 | * @inherits SchemaType | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | function Mixed (path, options) { |
| 19 | 2 | if (options && options.default) { |
| 20 | 0 | var def = options.default; |
| 21 | 0 | if (Array.isArray(def) && 0 === def.length) { |
| 22 | // make sure empty array defaults are handled | |
| 23 | 0 | options.default = Array; |
| 24 | 0 | } else if (!options.shared && |
| 25 | utils.isObject(def) && | |
| 26 | 0 === Object.keys(def).length) { | |
| 27 | // prevent odd "shared" objects between documents | |
| 28 | 0 | options.default = function () { |
| 29 | 0 | return {} |
| 30 | } | |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 | 2 | SchemaType.call(this, path, options); |
| 35 | }; | |
| 36 | ||
| 37 | /*! | |
| 38 | * Inherits from SchemaType. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | Mixed.prototype.__proto__ = SchemaType.prototype; |
| 42 | ||
| 43 | /** | |
| 44 | * Required validator | |
| 45 | * | |
| 46 | * @api private | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | Mixed.prototype.checkRequired = function (val) { |
| 50 | 0 | return (val !== undefined) && (val !== null); |
| 51 | }; | |
| 52 | ||
| 53 | /** | |
| 54 | * Casts `val` for Mixed. | |
| 55 | * | |
| 56 | * _this is a no-op_ | |
| 57 | * | |
| 58 | * @param {Object} value to cast | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | Mixed.prototype.cast = function (val) { |
| 63 | 0 | return val; |
| 64 | }; | |
| 65 | ||
| 66 | /** | |
| 67 | * Casts contents for queries. | |
| 68 | * | |
| 69 | * @param {String} $cond | |
| 70 | * @param {any} [val] | |
| 71 | * @api private | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | Mixed.prototype.castForQuery = function ($cond, val) { |
| 75 | 0 | if (arguments.length === 2) return val; |
| 76 | 0 | return $cond; |
| 77 | }; | |
| 78 | ||
| 79 | /*! | |
| 80 | * Module exports. | |
| 81 | */ | |
| 82 | ||
| 83 | 1 | module.exports = Mixed; |
| 84 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module requirements. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype') |
| 6 | , CastError = SchemaType.CastError | |
| 7 | , errorMessages = require('../error').messages | |
| 8 | , utils = require('../utils') | |
| 9 | , Document | |
| 10 | ||
| 11 | /** | |
| 12 | * Number SchemaType constructor. | |
| 13 | * | |
| 14 | * @param {String} key | |
| 15 | * @param {Object} options | |
| 16 | * @inherits SchemaType | |
| 17 | * @api private | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | function SchemaNumber (key, options) { |
| 21 | 7 | SchemaType.call(this, key, options, 'Number'); |
| 22 | }; | |
| 23 | ||
| 24 | /*! | |
| 25 | * Inherits from SchemaType. | |
| 26 | */ | |
| 27 | ||
| 28 | 1 | SchemaNumber.prototype.__proto__ = SchemaType.prototype; |
| 29 | ||
| 30 | /** | |
| 31 | * Required validator for number | |
| 32 | * | |
| 33 | * @api private | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | SchemaNumber.prototype.checkRequired = function checkRequired (value, doc) { |
| 37 | 0 | if (SchemaType._isRef(this, value, doc, true)) { |
| 38 | 0 | return null != value; |
| 39 | } else { | |
| 40 | 0 | return typeof value == 'number' || value instanceof Number; |
| 41 | } | |
| 42 | }; | |
| 43 | ||
| 44 | /** | |
| 45 | * Sets a minimum number validator. | |
| 46 | * | |
| 47 | * ####Example: | |
| 48 | * | |
| 49 | * var s = new Schema({ n: { type: Number, min: 10 }) | |
| 50 | * var M = db.model('M', s) | |
| 51 | * var m = new M({ n: 9 }) | |
| 52 | * m.save(function (err) { | |
| 53 | * console.error(err) // validator error | |
| 54 | * m.n = 10; | |
| 55 | * m.save() // success | |
| 56 | * }) | |
| 57 | * | |
| 58 | * // custom error messages | |
| 59 | * // We can also use the special {MIN} token which will be replaced with the invalid value | |
| 60 | * var min = [10, 'The value of path `{PATH}` ({VALUE}) is beneath the limit ({MIN}).']; | |
| 61 | * var schema = new Schema({ n: { type: Number, min: min }) | |
| 62 | * var M = mongoose.model('Measurement', schema); | |
| 63 | * var s= new M({ n: 4 }); | |
| 64 | * s.validate(function (err) { | |
| 65 | * console.log(String(err)) // ValidationError: The value of path `n` (4) is beneath the limit (10). | |
| 66 | * }) | |
| 67 | * | |
| 68 | * @param {Number} value minimum number | |
| 69 | * @param {String} [message] optional custom error message | |
| 70 | * @return {SchemaType} this | |
| 71 | * @see Customized Error Messages #error_messages_MongooseError-messages | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | ||
| 75 | 1 | SchemaNumber.prototype.min = function (value, message) { |
| 76 | 0 | if (this.minValidator) { |
| 77 | 0 | this.validators = this.validators.filter(function (v) { |
| 78 | 0 | return v[0] != this.minValidator; |
| 79 | }, this); | |
| 80 | } | |
| 81 | ||
| 82 | 0 | if (null != value) { |
| 83 | 0 | var msg = message || errorMessages.Number.min; |
| 84 | 0 | msg = msg.replace(/{MIN}/, value); |
| 85 | 0 | this.validators.push([this.minValidator = function (v) { |
| 86 | 0 | return v === null || v >= value; |
| 87 | }, msg, 'min']); | |
| 88 | } | |
| 89 | ||
| 90 | 0 | return this; |
| 91 | }; | |
| 92 | ||
| 93 | /** | |
| 94 | * Sets a maximum number validator. | |
| 95 | * | |
| 96 | * ####Example: | |
| 97 | * | |
| 98 | * var s = new Schema({ n: { type: Number, max: 10 }) | |
| 99 | * var M = db.model('M', s) | |
| 100 | * var m = new M({ n: 11 }) | |
| 101 | * m.save(function (err) { | |
| 102 | * console.error(err) // validator error | |
| 103 | * m.n = 10; | |
| 104 | * m.save() // success | |
| 105 | * }) | |
| 106 | * | |
| 107 | * // custom error messages | |
| 108 | * // We can also use the special {MAX} token which will be replaced with the invalid value | |
| 109 | * var max = [10, 'The value of path `{PATH}` ({VALUE}) exceeds the limit ({MAX}).']; | |
| 110 | * var schema = new Schema({ n: { type: Number, max: max }) | |
| 111 | * var M = mongoose.model('Measurement', schema); | |
| 112 | * var s= new M({ n: 4 }); | |
| 113 | * s.validate(function (err) { | |
| 114 | * console.log(String(err)) // ValidationError: The value of path `n` (4) exceeds the limit (10). | |
| 115 | * }) | |
| 116 | * | |
| 117 | * @param {Number} maximum number | |
| 118 | * @param {String} [message] optional custom error message | |
| 119 | * @return {SchemaType} this | |
| 120 | * @see Customized Error Messages #error_messages_MongooseError-messages | |
| 121 | * @api public | |
| 122 | */ | |
| 123 | ||
| 124 | 1 | SchemaNumber.prototype.max = function (value, message) { |
| 125 | 0 | if (this.maxValidator) { |
| 126 | 0 | this.validators = this.validators.filter(function(v){ |
| 127 | 0 | return v[0] != this.maxValidator; |
| 128 | }, this); | |
| 129 | } | |
| 130 | ||
| 131 | 0 | if (null != value) { |
| 132 | 0 | var msg = message || errorMessages.Number.max; |
| 133 | 0 | msg = msg.replace(/{MAX}/, value); |
| 134 | 0 | this.validators.push([this.maxValidator = function(v){ |
| 135 | 0 | return v === null || v <= value; |
| 136 | }, msg, 'max']); | |
| 137 | } | |
| 138 | ||
| 139 | 0 | return this; |
| 140 | }; | |
| 141 | ||
| 142 | /** | |
| 143 | * Casts to number | |
| 144 | * | |
| 145 | * @param {Object} value value to cast | |
| 146 | * @param {Document} doc document that triggers the casting | |
| 147 | * @param {Boolean} init | |
| 148 | * @api private | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | SchemaNumber.prototype.cast = function (value, doc, init) { |
| 152 | 0 | if (SchemaType._isRef(this, value, doc, init)) { |
| 153 | // wait! we may need to cast this to a document | |
| 154 | ||
| 155 | 0 | if (null == value) { |
| 156 | 0 | return value; |
| 157 | } | |
| 158 | ||
| 159 | // lazy load | |
| 160 | 0 | Document || (Document = require('./../document')); |
| 161 | ||
| 162 | 0 | if (value instanceof Document) { |
| 163 | 0 | value.$__.wasPopulated = true; |
| 164 | 0 | return value; |
| 165 | } | |
| 166 | ||
| 167 | // setting a populated path | |
| 168 | 0 | if ('number' == typeof value) { |
| 169 | 0 | return value; |
| 170 | 0 | } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { |
| 171 | 0 | throw new CastError('number', value, this.path); |
| 172 | } | |
| 173 | ||
| 174 | // Handle the case where user directly sets a populated | |
| 175 | // path to a plain object; cast to the Model used in | |
| 176 | // the population query. | |
| 177 | 0 | var path = doc.$__fullPath(this.path); |
| 178 | 0 | var owner = doc.ownerDocument ? doc.ownerDocument() : doc; |
| 179 | 0 | var pop = owner.populated(path, true); |
| 180 | 0 | var ret = new pop.options.model(value); |
| 181 | 0 | ret.$__.wasPopulated = true; |
| 182 | 0 | return ret; |
| 183 | } | |
| 184 | ||
| 185 | 0 | var val = value && value._id |
| 186 | ? value._id // documents | |
| 187 | : value; | |
| 188 | ||
| 189 | 0 | if (!isNaN(val)){ |
| 190 | 0 | if (null === val) return val; |
| 191 | 0 | if ('' === val) return null; |
| 192 | 0 | if ('string' == typeof val) val = Number(val); |
| 193 | 0 | if (val instanceof Number) return val |
| 194 | 0 | if ('number' == typeof val) return val; |
| 195 | 0 | if (val.toString && !Array.isArray(val) && |
| 196 | val.toString() == Number(val)) { | |
| 197 | 0 | return new Number(val) |
| 198 | } | |
| 199 | } | |
| 200 | ||
| 201 | 0 | throw new CastError('number', value, this.path); |
| 202 | }; | |
| 203 | ||
| 204 | /*! | |
| 205 | * ignore | |
| 206 | */ | |
| 207 | ||
| 208 | 1 | function handleSingle (val) { |
| 209 | 0 | return this.cast(val) |
| 210 | } | |
| 211 | ||
| 212 | 1 | function handleArray (val) { |
| 213 | 0 | var self = this; |
| 214 | 0 | return val.map(function (m) { |
| 215 | 0 | return self.cast(m) |
| 216 | }); | |
| 217 | } | |
| 218 | ||
| 219 | 1 | SchemaNumber.prototype.$conditionalHandlers = { |
| 220 | '$lt' : handleSingle | |
| 221 | , '$lte': handleSingle | |
| 222 | , '$gt' : handleSingle | |
| 223 | , '$gte': handleSingle | |
| 224 | , '$ne' : handleSingle | |
| 225 | , '$in' : handleArray | |
| 226 | , '$nin': handleArray | |
| 227 | , '$mod': handleArray | |
| 228 | , '$all': handleArray | |
| 229 | }; | |
| 230 | ||
| 231 | /** | |
| 232 | * Casts contents for queries. | |
| 233 | * | |
| 234 | * @param {String} $conditional | |
| 235 | * @param {any} [value] | |
| 236 | * @api private | |
| 237 | */ | |
| 238 | ||
| 239 | 1 | SchemaNumber.prototype.castForQuery = function ($conditional, val) { |
| 240 | 0 | var handler; |
| 241 | 0 | if (arguments.length === 2) { |
| 242 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 243 | 0 | if (!handler) |
| 244 | 0 | throw new Error("Can't use " + $conditional + " with Number."); |
| 245 | 0 | return handler.call(this, val); |
| 246 | } else { | |
| 247 | 0 | val = this.cast($conditional); |
| 248 | 0 | return val == null ? val : val |
| 249 | } | |
| 250 | }; | |
| 251 | ||
| 252 | /*! | |
| 253 | * Module exports. | |
| 254 | */ | |
| 255 | ||
| 256 | 1 | module.exports = SchemaNumber; |
| 257 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var SchemaType = require('../schematype') |
| 6 | , CastError = SchemaType.CastError | |
| 7 | , driver = global.MONGOOSE_DRIVER_PATH || './../drivers/node-mongodb-native' | |
| 8 | , oid = require('../types/objectid') | |
| 9 | , utils = require('../utils') | |
| 10 | , Document | |
| 11 | ||
| 12 | /** | |
| 13 | * ObjectId SchemaType constructor. | |
| 14 | * | |
| 15 | * @param {String} key | |
| 16 | * @param {Object} options | |
| 17 | * @inherits SchemaType | |
| 18 | * @api private | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function ObjectId (key, options) { |
| 22 | 0 | SchemaType.call(this, key, options, 'ObjectID'); |
| 23 | }; | |
| 24 | ||
| 25 | /*! | |
| 26 | * Inherits from SchemaType. | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | ObjectId.prototype.__proto__ = SchemaType.prototype; |
| 30 | ||
| 31 | /** | |
| 32 | * Adds an auto-generated ObjectId default if turnOn is true. | |
| 33 | * @param {Boolean} turnOn auto generated ObjectId defaults | |
| 34 | * @api public | |
| 35 | * @return {SchemaType} this | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | ObjectId.prototype.auto = function (turnOn) { |
| 39 | 0 | if (turnOn) { |
| 40 | 0 | this.default(defaultId); |
| 41 | 0 | this.set(resetId) |
| 42 | } | |
| 43 | ||
| 44 | 0 | return this; |
| 45 | }; | |
| 46 | ||
| 47 | /** | |
| 48 | * Check required | |
| 49 | * | |
| 50 | * @api private | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | ObjectId.prototype.checkRequired = function checkRequired (value, doc) { |
| 54 | 0 | if (SchemaType._isRef(this, value, doc, true)) { |
| 55 | 0 | return null != value; |
| 56 | } else { | |
| 57 | 0 | return value instanceof oid; |
| 58 | } | |
| 59 | }; | |
| 60 | ||
| 61 | /** | |
| 62 | * Casts to ObjectId | |
| 63 | * | |
| 64 | * @param {Object} value | |
| 65 | * @param {Object} doc | |
| 66 | * @param {Boolean} init whether this is an initialization cast | |
| 67 | * @api private | |
| 68 | */ | |
| 69 | ||
| 70 | 1 | ObjectId.prototype.cast = function (value, doc, init) { |
| 71 | 0 | if (SchemaType._isRef(this, value, doc, init)) { |
| 72 | // wait! we may need to cast this to a document | |
| 73 | ||
| 74 | 0 | if (null == value) { |
| 75 | 0 | return value; |
| 76 | } | |
| 77 | ||
| 78 | // lazy load | |
| 79 | 0 | Document || (Document = require('./../document')); |
| 80 | ||
| 81 | 0 | if (value instanceof Document) { |
| 82 | 0 | value.$__.wasPopulated = true; |
| 83 | 0 | return value; |
| 84 | } | |
| 85 | ||
| 86 | // setting a populated path | |
| 87 | 0 | if (value instanceof oid) { |
| 88 | 0 | return value; |
| 89 | 0 | } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { |
| 90 | 0 | throw new CastError('ObjectId', value, this.path); |
| 91 | } | |
| 92 | ||
| 93 | // Handle the case where user directly sets a populated | |
| 94 | // path to a plain object; cast to the Model used in | |
| 95 | // the population query. | |
| 96 | 0 | var path = doc.$__fullPath(this.path); |
| 97 | 0 | var owner = doc.ownerDocument ? doc.ownerDocument() : doc; |
| 98 | 0 | var pop = owner.populated(path, true); |
| 99 | 0 | var ret = new pop.options.model(value); |
| 100 | 0 | ret.$__.wasPopulated = true; |
| 101 | 0 | return ret; |
| 102 | } | |
| 103 | ||
| 104 | 0 | if (value === null) return value; |
| 105 | ||
| 106 | 0 | if (value instanceof oid) |
| 107 | 0 | return value; |
| 108 | ||
| 109 | 0 | if (value._id && value._id instanceof oid) |
| 110 | 0 | return value._id; |
| 111 | ||
| 112 | 0 | if (value.toString) { |
| 113 | 0 | try { |
| 114 | 0 | return oid.createFromHexString(value.toString()); |
| 115 | } catch (err) { | |
| 116 | 0 | throw new CastError('ObjectId', value, this.path); |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | 0 | throw new CastError('ObjectId', value, this.path); |
| 121 | }; | |
| 122 | ||
| 123 | /*! | |
| 124 | * ignore | |
| 125 | */ | |
| 126 | ||
| 127 | 1 | function handleSingle (val) { |
| 128 | 0 | return this.cast(val); |
| 129 | } | |
| 130 | ||
| 131 | 1 | function handleArray (val) { |
| 132 | 0 | var self = this; |
| 133 | 0 | return val.map(function (m) { |
| 134 | 0 | return self.cast(m); |
| 135 | }); | |
| 136 | } | |
| 137 | ||
| 138 | 1 | ObjectId.prototype.$conditionalHandlers = { |
| 139 | '$ne': handleSingle | |
| 140 | , '$in': handleArray | |
| 141 | , '$nin': handleArray | |
| 142 | , '$gt': handleSingle | |
| 143 | , '$lt': handleSingle | |
| 144 | , '$gte': handleSingle | |
| 145 | , '$lte': handleSingle | |
| 146 | , '$all': handleArray | |
| 147 | }; | |
| 148 | ||
| 149 | /** | |
| 150 | * Casts contents for queries. | |
| 151 | * | |
| 152 | * @param {String} $conditional | |
| 153 | * @param {any} [val] | |
| 154 | * @api private | |
| 155 | */ | |
| 156 | ||
| 157 | 1 | ObjectId.prototype.castForQuery = function ($conditional, val) { |
| 158 | 0 | var handler; |
| 159 | 0 | if (arguments.length === 2) { |
| 160 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 161 | 0 | if (!handler) |
| 162 | 0 | throw new Error("Can't use " + $conditional + " with ObjectId."); |
| 163 | 0 | return handler.call(this, val); |
| 164 | } else { | |
| 165 | 0 | return this.cast($conditional); |
| 166 | } | |
| 167 | }; | |
| 168 | ||
| 169 | /*! | |
| 170 | * ignore | |
| 171 | */ | |
| 172 | ||
| 173 | 1 | function defaultId () { |
| 174 | 0 | return new oid(); |
| 175 | }; | |
| 176 | ||
| 177 | 1 | function resetId (v) { |
| 178 | 0 | this.$__._id = null; |
| 179 | 0 | return v; |
| 180 | } | |
| 181 | ||
| 182 | /*! | |
| 183 | * Module exports. | |
| 184 | */ | |
| 185 | ||
| 186 | 1 | module.exports = ObjectId; |
| 187 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var SchemaType = require('../schematype') |
| 7 | , CastError = SchemaType.CastError | |
| 8 | , errorMessages = require('../error').messages | |
| 9 | , utils = require('../utils') | |
| 10 | , Document | |
| 11 | ||
| 12 | /** | |
| 13 | * String SchemaType constructor. | |
| 14 | * | |
| 15 | * @param {String} key | |
| 16 | * @param {Object} options | |
| 17 | * @inherits SchemaType | |
| 18 | * @api private | |
| 19 | */ | |
| 20 | ||
| 21 | 1 | function SchemaString (key, options) { |
| 22 | 5 | this.enumValues = []; |
| 23 | 5 | this.regExp = null; |
| 24 | 5 | SchemaType.call(this, key, options, 'String'); |
| 25 | }; | |
| 26 | ||
| 27 | /*! | |
| 28 | * Inherits from SchemaType. | |
| 29 | */ | |
| 30 | ||
| 31 | 1 | SchemaString.prototype.__proto__ = SchemaType.prototype; |
| 32 | ||
| 33 | /** | |
| 34 | * Adds an enum validator | |
| 35 | * | |
| 36 | * ####Example: | |
| 37 | * | |
| 38 | * var states = 'opening open closing closed'.split(' ') | |
| 39 | * var s = new Schema({ state: { type: String, enum: states }}) | |
| 40 | * var M = db.model('M', s) | |
| 41 | * var m = new M({ state: 'invalid' }) | |
| 42 | * m.save(function (err) { | |
| 43 | * console.error(String(err)) // ValidationError: `invalid` is not a valid enum value for path `state`. | |
| 44 | * m.state = 'open' | |
| 45 | * m.save(callback) // success | |
| 46 | * }) | |
| 47 | * | |
| 48 | * // or with custom error messages | |
| 49 | * var enu = { | |
| 50 | * values: 'opening open closing closed'.split(' '), | |
| 51 | * message: 'enum validator failed for path `{PATH}` with value `{VALUE}`' | |
| 52 | * } | |
| 53 | * var s = new Schema({ state: { type: String, enum: enu }) | |
| 54 | * var M = db.model('M', s) | |
| 55 | * var m = new M({ state: 'invalid' }) | |
| 56 | * m.save(function (err) { | |
| 57 | * console.error(String(err)) // ValidationError: enum validator failed for path `state` with value `invalid` | |
| 58 | * m.state = 'open' | |
| 59 | * m.save(callback) // success | |
| 60 | * }) | |
| 61 | * | |
| 62 | * @param {String|Object} [args...] enumeration values | |
| 63 | * @return {SchemaType} this | |
| 64 | * @see Customized Error Messages #error_messages_MongooseError-messages | |
| 65 | * @api public | |
| 66 | */ | |
| 67 | ||
| 68 | 1 | SchemaString.prototype.enum = function () { |
| 69 | 0 | if (this.enumValidator) { |
| 70 | 0 | this.validators = this.validators.filter(function(v){ |
| 71 | 0 | return v[0] != this.enumValidator; |
| 72 | }, this); | |
| 73 | 0 | this.enumValidator = false; |
| 74 | } | |
| 75 | ||
| 76 | 0 | if (undefined === arguments[0] || false === arguments[0]) { |
| 77 | 0 | return this; |
| 78 | } | |
| 79 | ||
| 80 | 0 | var values; |
| 81 | 0 | var errorMessage; |
| 82 | ||
| 83 | 0 | if (utils.isObject(arguments[0])) { |
| 84 | 0 | values = arguments[0].values; |
| 85 | 0 | errorMessage = arguments[0].message; |
| 86 | } else { | |
| 87 | 0 | values = arguments; |
| 88 | 0 | errorMessage = errorMessages.String.enum; |
| 89 | } | |
| 90 | ||
| 91 | 0 | for (var i = 0; i < values.length; i++) { |
| 92 | 0 | if (undefined !== values[i]) { |
| 93 | 0 | this.enumValues.push(this.cast(values[i])); |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | 0 | var vals = this.enumValues; |
| 98 | 0 | this.enumValidator = function (v) { |
| 99 | 0 | return undefined === v || ~vals.indexOf(v); |
| 100 | }; | |
| 101 | 0 | this.validators.push([this.enumValidator, errorMessage, 'enum']); |
| 102 | ||
| 103 | 0 | return this; |
| 104 | }; | |
| 105 | ||
| 106 | /** | |
| 107 | * Adds a lowercase setter. | |
| 108 | * | |
| 109 | * ####Example: | |
| 110 | * | |
| 111 | * var s = new Schema({ email: { type: String, lowercase: true }}) | |
| 112 | * var M = db.model('M', s); | |
| 113 | * var m = new M({ email: 'SomeEmail@example.COM' }); | |
| 114 | * console.log(m.email) // someemail@example.com | |
| 115 | * | |
| 116 | * @api public | |
| 117 | * @return {SchemaType} this | |
| 118 | */ | |
| 119 | ||
| 120 | 1 | SchemaString.prototype.lowercase = function () { |
| 121 | 0 | return this.set(function (v, self) { |
| 122 | 0 | if ('string' != typeof v) v = self.cast(v) |
| 123 | 0 | if (v) return v.toLowerCase(); |
| 124 | 0 | return v; |
| 125 | }); | |
| 126 | }; | |
| 127 | ||
| 128 | /** | |
| 129 | * Adds an uppercase setter. | |
| 130 | * | |
| 131 | * ####Example: | |
| 132 | * | |
| 133 | * var s = new Schema({ caps: { type: String, uppercase: true }}) | |
| 134 | * var M = db.model('M', s); | |
| 135 | * var m = new M({ caps: 'an example' }); | |
| 136 | * console.log(m.caps) // AN EXAMPLE | |
| 137 | * | |
| 138 | * @api public | |
| 139 | * @return {SchemaType} this | |
| 140 | */ | |
| 141 | ||
| 142 | 1 | SchemaString.prototype.uppercase = function () { |
| 143 | 0 | return this.set(function (v, self) { |
| 144 | 0 | if ('string' != typeof v) v = self.cast(v) |
| 145 | 0 | if (v) return v.toUpperCase(); |
| 146 | 0 | return v; |
| 147 | }); | |
| 148 | }; | |
| 149 | ||
| 150 | /** | |
| 151 | * Adds a trim setter. | |
| 152 | * | |
| 153 | * The string value will be trimmed when set. | |
| 154 | * | |
| 155 | * ####Example: | |
| 156 | * | |
| 157 | * var s = new Schema({ name: { type: String, trim: true }}) | |
| 158 | * var M = db.model('M', s) | |
| 159 | * var string = ' some name ' | |
| 160 | * console.log(string.length) // 11 | |
| 161 | * var m = new M({ name: string }) | |
| 162 | * console.log(m.name.length) // 9 | |
| 163 | * | |
| 164 | * @api public | |
| 165 | * @return {SchemaType} this | |
| 166 | */ | |
| 167 | ||
| 168 | 1 | SchemaString.prototype.trim = function () { |
| 169 | 0 | return this.set(function (v, self) { |
| 170 | 0 | if ('string' != typeof v) v = self.cast(v) |
| 171 | 0 | if (v) return v.trim(); |
| 172 | 0 | return v; |
| 173 | }); | |
| 174 | }; | |
| 175 | ||
| 176 | /** | |
| 177 | * Sets a regexp validator. | |
| 178 | * | |
| 179 | * Any value that does not pass `regExp`.test(val) will fail validation. | |
| 180 | * | |
| 181 | * ####Example: | |
| 182 | * | |
| 183 | * var s = new Schema({ name: { type: String, match: /^a/ }}) | |
| 184 | * var M = db.model('M', s) | |
| 185 | * var m = new M({ name: 'I am invalid' }) | |
| 186 | * m.validate(function (err) { | |
| 187 | * console.error(String(err)) // "ValidationError: Path `name` is invalid (I am invalid)." | |
| 188 | * m.name = 'apples' | |
| 189 | * m.validate(function (err) { | |
| 190 | * assert.ok(err) // success | |
| 191 | * }) | |
| 192 | * }) | |
| 193 | * | |
| 194 | * // using a custom error message | |
| 195 | * var match = [ /\.html$/, "That file doesn't end in .html ({VALUE})" ]; | |
| 196 | * var s = new Schema({ file: { type: String, match: match }}) | |
| 197 | * var M = db.model('M', s); | |
| 198 | * var m = new M({ file: 'invalid' }); | |
| 199 | * m.validate(function (err) { | |
| 200 | * console.log(String(err)) // "ValidationError: That file doesn't end in .html (invalid)" | |
| 201 | * }) | |
| 202 | * | |
| 203 | * Empty strings, `undefined`, and `null` values always pass the match validator. If you require these values, enable the `required` validator also. | |
| 204 | * | |
| 205 | * var s = new Schema({ name: { type: String, match: /^a/, required: true }}) | |
| 206 | * | |
| 207 | * @param {RegExp} regExp regular expression to test against | |
| 208 | * @param {String} [message] optional custom error message | |
| 209 | * @return {SchemaType} this | |
| 210 | * @see Customized Error Messages #error_messages_MongooseError-messages | |
| 211 | * @api public | |
| 212 | */ | |
| 213 | ||
| 214 | 1 | SchemaString.prototype.match = function match (regExp, message) { |
| 215 | // yes, we allow multiple match validators | |
| 216 | ||
| 217 | 0 | var msg = message || errorMessages.String.match; |
| 218 | ||
| 219 | 0 | function matchValidator (v){ |
| 220 | 0 | return null != v && '' !== v |
| 221 | ? regExp.test(v) | |
| 222 | : true | |
| 223 | } | |
| 224 | ||
| 225 | 0 | this.validators.push([matchValidator, msg, 'regexp']); |
| 226 | 0 | return this; |
| 227 | }; | |
| 228 | ||
| 229 | /** | |
| 230 | * Check required | |
| 231 | * | |
| 232 | * @param {String|null|undefined} value | |
| 233 | * @api private | |
| 234 | */ | |
| 235 | ||
| 236 | 1 | SchemaString.prototype.checkRequired = function checkRequired (value, doc) { |
| 237 | 0 | if (SchemaType._isRef(this, value, doc, true)) { |
| 238 | 0 | return null != value; |
| 239 | } else { | |
| 240 | 0 | return (value instanceof String || typeof value == 'string') && value.length; |
| 241 | } | |
| 242 | }; | |
| 243 | ||
| 244 | /** | |
| 245 | * Casts to String | |
| 246 | * | |
| 247 | * @api private | |
| 248 | */ | |
| 249 | ||
| 250 | 1 | SchemaString.prototype.cast = function (value, doc, init) { |
| 251 | 0 | if (SchemaType._isRef(this, value, doc, init)) { |
| 252 | // wait! we may need to cast this to a document | |
| 253 | ||
| 254 | 0 | if (null == value) { |
| 255 | 0 | return value; |
| 256 | } | |
| 257 | ||
| 258 | // lazy load | |
| 259 | 0 | Document || (Document = require('./../document')); |
| 260 | ||
| 261 | 0 | if (value instanceof Document) { |
| 262 | 0 | value.$__.wasPopulated = true; |
| 263 | 0 | return value; |
| 264 | } | |
| 265 | ||
| 266 | // setting a populated path | |
| 267 | 0 | if ('string' == typeof value) { |
| 268 | 0 | return value; |
| 269 | 0 | } else if (Buffer.isBuffer(value) || !utils.isObject(value)) { |
| 270 | 0 | throw new CastError('string', value, this.path); |
| 271 | } | |
| 272 | ||
| 273 | // Handle the case where user directly sets a populated | |
| 274 | // path to a plain object; cast to the Model used in | |
| 275 | // the population query. | |
| 276 | 0 | var path = doc.$__fullPath(this.path); |
| 277 | 0 | var owner = doc.ownerDocument ? doc.ownerDocument() : doc; |
| 278 | 0 | var pop = owner.populated(path, true); |
| 279 | 0 | var ret = new pop.options.model(value); |
| 280 | 0 | ret.$__.wasPopulated = true; |
| 281 | 0 | return ret; |
| 282 | } | |
| 283 | ||
| 284 | 0 | if (value === null) { |
| 285 | 0 | return value; |
| 286 | } | |
| 287 | ||
| 288 | 0 | if ('undefined' !== typeof value) { |
| 289 | // handle documents being passed | |
| 290 | 0 | if (value._id && 'string' == typeof value._id) { |
| 291 | 0 | return value._id; |
| 292 | } | |
| 293 | 0 | if (value.toString) { |
| 294 | 0 | return value.toString(); |
| 295 | } | |
| 296 | } | |
| 297 | ||
| 298 | ||
| 299 | 0 | throw new CastError('string', value, this.path); |
| 300 | }; | |
| 301 | ||
| 302 | /*! | |
| 303 | * ignore | |
| 304 | */ | |
| 305 | ||
| 306 | 1 | function handleSingle (val) { |
| 307 | 0 | return this.castForQuery(val); |
| 308 | } | |
| 309 | ||
| 310 | 1 | function handleArray (val) { |
| 311 | 0 | var self = this; |
| 312 | 0 | return val.map(function (m) { |
| 313 | 0 | return self.castForQuery(m); |
| 314 | }); | |
| 315 | } | |
| 316 | ||
| 317 | 1 | SchemaString.prototype.$conditionalHandlers = { |
| 318 | '$ne' : handleSingle | |
| 319 | , '$in' : handleArray | |
| 320 | , '$nin': handleArray | |
| 321 | , '$gt' : handleSingle | |
| 322 | , '$lt' : handleSingle | |
| 323 | , '$gte': handleSingle | |
| 324 | , '$lte': handleSingle | |
| 325 | , '$all': handleArray | |
| 326 | , '$regex': handleSingle | |
| 327 | , '$options': handleSingle | |
| 328 | }; | |
| 329 | ||
| 330 | /** | |
| 331 | * Casts contents for queries. | |
| 332 | * | |
| 333 | * @param {String} $conditional | |
| 334 | * @param {any} [val] | |
| 335 | * @api private | |
| 336 | */ | |
| 337 | ||
| 338 | 1 | SchemaString.prototype.castForQuery = function ($conditional, val) { |
| 339 | 0 | var handler; |
| 340 | 0 | if (arguments.length === 2) { |
| 341 | 0 | handler = this.$conditionalHandlers[$conditional]; |
| 342 | 0 | if (!handler) |
| 343 | 0 | throw new Error("Can't use " + $conditional + " with String."); |
| 344 | 0 | return handler.call(this, val); |
| 345 | } else { | |
| 346 | 0 | val = $conditional; |
| 347 | 0 | if (val instanceof RegExp) return val; |
| 348 | 0 | return this.cast(val); |
| 349 | } | |
| 350 | }; | |
| 351 | ||
| 352 | /*! | |
| 353 | * Module exports. | |
| 354 | */ | |
| 355 | ||
| 356 | 1 | module.exports = SchemaString; |
| 357 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Schema = require('./schema') |
| 7 | ||
| 8 | /** | |
| 9 | * Default model for querying the system.profiles collection. | |
| 10 | * | |
| 11 | * @property system.profile | |
| 12 | * @receiver exports | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | exports['system.profile'] = new Schema({ |
| 17 | ts: Date | |
| 18 | , info: String // deprecated | |
| 19 | , millis: Number | |
| 20 | , op: String | |
| 21 | , ns: String | |
| 22 | , query: Schema.Types.Mixed | |
| 23 | , updateobj: Schema.Types.Mixed | |
| 24 | , ntoreturn: Number | |
| 25 | , nreturned: Number | |
| 26 | , nscanned: Number | |
| 27 | , responseLength: Number | |
| 28 | , client: String | |
| 29 | , user: String | |
| 30 | , idhack: Boolean | |
| 31 | , scanAndOrder: Boolean | |
| 32 | , keyUpdates: Number | |
| 33 | , cursorid: Number | |
| 34 | }, { noVirtualId: true, noId: true }); | |
| 35 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var utils = require('./utils'); |
| 6 | 1 | var error = require('./error'); |
| 7 | 1 | var errorMessages = error.messages; |
| 8 | 1 | var CastError = error.CastError; |
| 9 | 1 | var ValidatorError = error.ValidatorError; |
| 10 | ||
| 11 | /** | |
| 12 | * SchemaType constructor | |
| 13 | * | |
| 14 | * @param {String} path | |
| 15 | * @param {Object} [options] | |
| 16 | * @param {String} [instance] | |
| 17 | * @api public | |
| 18 | */ | |
| 19 | ||
| 20 | 1 | function SchemaType (path, options, instance) { |
| 21 | 17 | this.path = path; |
| 22 | 17 | this.instance = instance; |
| 23 | 17 | this.validators = []; |
| 24 | 17 | this.setters = []; |
| 25 | 17 | this.getters = []; |
| 26 | 17 | this.options = options; |
| 27 | 17 | this._index = null; |
| 28 | 17 | this.selected; |
| 29 | ||
| 30 | 34 | for (var i in options) if (this[i] && 'function' == typeof this[i]) { |
| 31 | // { unique: true, index: true } | |
| 32 | 0 | if ('index' == i && this._index) continue; |
| 33 | ||
| 34 | 0 | var opts = Array.isArray(options[i]) |
| 35 | ? options[i] | |
| 36 | : [options[i]]; | |
| 37 | ||
| 38 | 0 | this[i].apply(this, opts); |
| 39 | } | |
| 40 | }; | |
| 41 | ||
| 42 | /** | |
| 43 | * Sets a default value for this SchemaType. | |
| 44 | * | |
| 45 | * ####Example: | |
| 46 | * | |
| 47 | * var schema = new Schema({ n: { type: Number, default: 10 }) | |
| 48 | * var M = db.model('M', schema) | |
| 49 | * var m = new M; | |
| 50 | * console.log(m.n) // 10 | |
| 51 | * | |
| 52 | * Defaults can be either `functions` which return the value to use as the default or the literal value itself. Either way, the value will be cast based on its schema type before being set during document creation. | |
| 53 | * | |
| 54 | * ####Example: | |
| 55 | * | |
| 56 | * // values are cast: | |
| 57 | * var schema = new Schema({ aNumber: Number, default: "4.815162342" }) | |
| 58 | * var M = db.model('M', schema) | |
| 59 | * var m = new M; | |
| 60 | * console.log(m.aNumber) // 4.815162342 | |
| 61 | * | |
| 62 | * // default unique objects for Mixed types: | |
| 63 | * var schema = new Schema({ mixed: Schema.Types.Mixed }); | |
| 64 | * schema.path('mixed').default(function () { | |
| 65 | * return {}; | |
| 66 | * }); | |
| 67 | * | |
| 68 | * // if we don't use a function to return object literals for Mixed defaults, | |
| 69 | * // each document will receive a reference to the same object literal creating | |
| 70 | * // a "shared" object instance: | |
| 71 | * var schema = new Schema({ mixed: Schema.Types.Mixed }); | |
| 72 | * schema.path('mixed').default({}); | |
| 73 | * var M = db.model('M', schema); | |
| 74 | * var m1 = new M; | |
| 75 | * m1.mixed.added = 1; | |
| 76 | * console.log(m1.mixed); // { added: 1 } | |
| 77 | * var m2 = new M; | |
| 78 | * console.log(m2.mixed); // { added: 1 } | |
| 79 | * | |
| 80 | * @param {Function|any} val the default value | |
| 81 | * @return {defaultValue} | |
| 82 | * @api public | |
| 83 | */ | |
| 84 | ||
| 85 | 1 | SchemaType.prototype.default = function (val) { |
| 86 | 0 | if (1 === arguments.length) { |
| 87 | 0 | this.defaultValue = typeof val === 'function' |
| 88 | ? val | |
| 89 | : this.cast(val); | |
| 90 | 0 | return this; |
| 91 | 0 | } else if (arguments.length > 1) { |
| 92 | 0 | this.defaultValue = utils.args(arguments); |
| 93 | } | |
| 94 | 0 | return this.defaultValue; |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Declares the index options for this schematype. | |
| 99 | * | |
| 100 | * ####Example: | |
| 101 | * | |
| 102 | * var s = new Schema({ name: { type: String, index: true }) | |
| 103 | * var s = new Schema({ loc: { type: [Number], index: 'hashed' }) | |
| 104 | * var s = new Schema({ loc: { type: [Number], index: '2d', sparse: true }) | |
| 105 | * var s = new Schema({ loc: { type: [Number], index: { type: '2dsphere', sparse: true }}) | |
| 106 | * var s = new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }}) | |
| 107 | * Schema.path('my.path').index(true); | |
| 108 | * Schema.path('my.date').index({ expires: 60 }); | |
| 109 | * Schema.path('my.path').index({ unique: true, sparse: true }); | |
| 110 | * | |
| 111 | * ####NOTE: | |
| 112 | * | |
| 113 | * _Indexes are created in the background by default. Specify `background: false` to override._ | |
| 114 | * | |
| 115 | * [Direction doesn't matter for single key indexes](http://www.mongodb.org/display/DOCS/Indexes#Indexes-CompoundKeysIndexes) | |
| 116 | * | |
| 117 | * @param {Object|Boolean|String} options | |
| 118 | * @return {SchemaType} this | |
| 119 | * @api public | |
| 120 | */ | |
| 121 | ||
| 122 | 1 | SchemaType.prototype.index = function (options) { |
| 123 | 0 | this._index = options; |
| 124 | 0 | utils.expires(this._index); |
| 125 | 0 | return this; |
| 126 | }; | |
| 127 | ||
| 128 | /** | |
| 129 | * Declares an unique index. | |
| 130 | * | |
| 131 | * ####Example: | |
| 132 | * | |
| 133 | * var s = new Schema({ name: { type: String, unique: true }) | |
| 134 | * Schema.path('name').index({ unique: true }); | |
| 135 | * | |
| 136 | * _NOTE: violating the constraint returns an `E11000` error from MongoDB when saving, not a Mongoose validation error._ | |
| 137 | * | |
| 138 | * @param {Boolean} bool | |
| 139 | * @return {SchemaType} this | |
| 140 | * @api public | |
| 141 | */ | |
| 142 | ||
| 143 | 1 | SchemaType.prototype.unique = function (bool) { |
| 144 | 0 | if (null == this._index || 'boolean' == typeof this._index) { |
| 145 | 0 | this._index = {}; |
| 146 | 0 | } else if ('string' == typeof this._index) { |
| 147 | 0 | this._index = { type: this._index }; |
| 148 | } | |
| 149 | ||
| 150 | 0 | this._index.unique = bool; |
| 151 | 0 | return this; |
| 152 | }; | |
| 153 | ||
| 154 | /** | |
| 155 | * Declares a sparse index. | |
| 156 | * | |
| 157 | * ####Example: | |
| 158 | * | |
| 159 | * var s = new Schema({ name: { type: String, sparse: true }) | |
| 160 | * Schema.path('name').index({ sparse: true }); | |
| 161 | * | |
| 162 | * @param {Boolean} bool | |
| 163 | * @return {SchemaType} this | |
| 164 | * @api public | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | SchemaType.prototype.sparse = function (bool) { |
| 168 | 0 | if (null == this._index || 'boolean' == typeof this._index) { |
| 169 | 0 | this._index = {}; |
| 170 | 0 | } else if ('string' == typeof this._index) { |
| 171 | 0 | this._index = { type: this._index }; |
| 172 | } | |
| 173 | ||
| 174 | 0 | this._index.sparse = bool; |
| 175 | 0 | return this; |
| 176 | }; | |
| 177 | ||
| 178 | /** | |
| 179 | * Adds a setter to this schematype. | |
| 180 | * | |
| 181 | * ####Example: | |
| 182 | * | |
| 183 | * function capitalize (val) { | |
| 184 | * if ('string' != typeof val) val = ''; | |
| 185 | * return val.charAt(0).toUpperCase() + val.substring(1); | |
| 186 | * } | |
| 187 | * | |
| 188 | * // defining within the schema | |
| 189 | * var s = new Schema({ name: { type: String, set: capitalize }}) | |
| 190 | * | |
| 191 | * // or by retreiving its SchemaType | |
| 192 | * var s = new Schema({ name: String }) | |
| 193 | * s.path('name').set(capitalize) | |
| 194 | * | |
| 195 | * Setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key. | |
| 196 | * | |
| 197 | * Suppose you are implementing user registration for a website. Users provide an email and password, which gets saved to mongodb. The email is a string that you will want to normalize to lower case, in order to avoid one email having more than one account -- e.g., otherwise, avenue@q.com can be registered for 2 accounts via avenue@q.com and AvEnUe@Q.CoM. | |
| 198 | * | |
| 199 | * You can set up email lower case normalization easily via a Mongoose setter. | |
| 200 | * | |
| 201 | * function toLower (v) { | |
| 202 | * return v.toLowerCase(); | |
| 203 | * } | |
| 204 | * | |
| 205 | * var UserSchema = new Schema({ | |
| 206 | * email: { type: String, set: toLower } | |
| 207 | * }) | |
| 208 | * | |
| 209 | * var User = db.model('User', UserSchema) | |
| 210 | * | |
| 211 | * var user = new User({email: 'AVENUE@Q.COM'}) | |
| 212 | * console.log(user.email); // 'avenue@q.com' | |
| 213 | * | |
| 214 | * // or | |
| 215 | * var user = new User | |
| 216 | * user.email = 'Avenue@Q.com' | |
| 217 | * console.log(user.email) // 'avenue@q.com' | |
| 218 | * | |
| 219 | * As you can see above, setters allow you to transform the data before it gets to the raw mongodb document and is set as a value on an actual key. | |
| 220 | * | |
| 221 | * _NOTE: we could have also just used the built-in `lowercase: true` SchemaType option instead of defining our own function._ | |
| 222 | * | |
| 223 | * new Schema({ email: { type: String, lowercase: true }}) | |
| 224 | * | |
| 225 | * Setters are also passed a second argument, the schematype on which the setter was defined. This allows for tailored behavior based on options passed in the schema. | |
| 226 | * | |
| 227 | * function inspector (val, schematype) { | |
| 228 | * if (schematype.options.required) { | |
| 229 | * return schematype.path + ' is required'; | |
| 230 | * } else { | |
| 231 | * return val; | |
| 232 | * } | |
| 233 | * } | |
| 234 | * | |
| 235 | * var VirusSchema = new Schema({ | |
| 236 | * name: { type: String, required: true, set: inspector }, | |
| 237 | * taxonomy: { type: String, set: inspector } | |
| 238 | * }) | |
| 239 | * | |
| 240 | * var Virus = db.model('Virus', VirusSchema); | |
| 241 | * var v = new Virus({ name: 'Parvoviridae', taxonomy: 'Parvovirinae' }); | |
| 242 | * | |
| 243 | * console.log(v.name); // name is required | |
| 244 | * console.log(v.taxonomy); // Parvovirinae | |
| 245 | * | |
| 246 | * @param {Function} fn | |
| 247 | * @return {SchemaType} this | |
| 248 | * @api public | |
| 249 | */ | |
| 250 | ||
| 251 | 1 | SchemaType.prototype.set = function (fn) { |
| 252 | 0 | if ('function' != typeof fn) |
| 253 | 0 | throw new TypeError('A setter must be a function.'); |
| 254 | 0 | this.setters.push(fn); |
| 255 | 0 | return this; |
| 256 | }; | |
| 257 | ||
| 258 | /** | |
| 259 | * Adds a getter to this schematype. | |
| 260 | * | |
| 261 | * ####Example: | |
| 262 | * | |
| 263 | * function dob (val) { | |
| 264 | * if (!val) return val; | |
| 265 | * return (val.getMonth() + 1) + "/" + val.getDate() + "/" + val.getFullYear(); | |
| 266 | * } | |
| 267 | * | |
| 268 | * // defining within the schema | |
| 269 | * var s = new Schema({ born: { type: Date, get: dob }) | |
| 270 | * | |
| 271 | * // or by retreiving its SchemaType | |
| 272 | * var s = new Schema({ born: Date }) | |
| 273 | * s.path('born').get(dob) | |
| 274 | * | |
| 275 | * Getters allow you to transform the representation of the data as it travels from the raw mongodb document to the value that you see. | |
| 276 | * | |
| 277 | * Suppose you are storing credit card numbers and you want to hide everything except the last 4 digits to the mongoose user. You can do so by defining a getter in the following way: | |
| 278 | * | |
| 279 | * function obfuscate (cc) { | |
| 280 | * return '****-****-****-' + cc.slice(cc.length-4, cc.length); | |
| 281 | * } | |
| 282 | * | |
| 283 | * var AccountSchema = new Schema({ | |
| 284 | * creditCardNumber: { type: String, get: obfuscate } | |
| 285 | * }); | |
| 286 | * | |
| 287 | * var Account = db.model('Account', AccountSchema); | |
| 288 | * | |
| 289 | * Account.findById(id, function (err, found) { | |
| 290 | * console.log(found.creditCardNumber); // '****-****-****-1234' | |
| 291 | * }); | |
| 292 | * | |
| 293 | * Getters are also passed a second argument, the schematype on which the getter was defined. This allows for tailored behavior based on options passed in the schema. | |
| 294 | * | |
| 295 | * function inspector (val, schematype) { | |
| 296 | * if (schematype.options.required) { | |
| 297 | * return schematype.path + ' is required'; | |
| 298 | * } else { | |
| 299 | * return schematype.path + ' is not'; | |
| 300 | * } | |
| 301 | * } | |
| 302 | * | |
| 303 | * var VirusSchema = new Schema({ | |
| 304 | * name: { type: String, required: true, get: inspector }, | |
| 305 | * taxonomy: { type: String, get: inspector } | |
| 306 | * }) | |
| 307 | * | |
| 308 | * var Virus = db.model('Virus', VirusSchema); | |
| 309 | * | |
| 310 | * Virus.findById(id, function (err, virus) { | |
| 311 | * console.log(virus.name); // name is required | |
| 312 | * console.log(virus.taxonomy); // taxonomy is not | |
| 313 | * }) | |
| 314 | * | |
| 315 | * @param {Function} fn | |
| 316 | * @return {SchemaType} this | |
| 317 | * @api public | |
| 318 | */ | |
| 319 | ||
| 320 | 1 | SchemaType.prototype.get = function (fn) { |
| 321 | 0 | if ('function' != typeof fn) |
| 322 | 0 | throw new TypeError('A getter must be a function.'); |
| 323 | 0 | this.getters.push(fn); |
| 324 | 0 | return this; |
| 325 | }; | |
| 326 | ||
| 327 | /** | |
| 328 | * Adds validator(s) for this document path. | |
| 329 | * | |
| 330 | * Validators always receive the value to validate as their first argument and must return `Boolean`. Returning `false` means validation failed. | |
| 331 | * | |
| 332 | * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used. | |
| 333 | * | |
| 334 | * ####Examples: | |
| 335 | * | |
| 336 | * // make sure every value is equal to "something" | |
| 337 | * function validator (val) { | |
| 338 | * return val == 'something'; | |
| 339 | * } | |
| 340 | * new Schema({ name: { type: String, validate: validator }}); | |
| 341 | * | |
| 342 | * // with a custom error message | |
| 343 | * | |
| 344 | * var custom = [validator, 'Uh oh, {PATH} does not equal "something".'] | |
| 345 | * new Schema({ name: { type: String, validate: custom }}); | |
| 346 | * | |
| 347 | * // adding many validators at a time | |
| 348 | * | |
| 349 | * var many = [ | |
| 350 | * { validator: validator, msg: 'uh oh' } | |
| 351 | * , { validator: anotherValidator, msg: 'failed' } | |
| 352 | * ] | |
| 353 | * new Schema({ name: { type: String, validate: many }}); | |
| 354 | * | |
| 355 | * // or utilizing SchemaType methods directly: | |
| 356 | * | |
| 357 | * var schema = new Schema({ name: 'string' }); | |
| 358 | * schema.path('name').validate(validator, 'validation of `{PATH}` failed with value `{VALUE}`'); | |
| 359 | * | |
| 360 | * ####Error message templates: | |
| 361 | * | |
| 362 | * From the examples above, you may have noticed that error messages support baseic templating. There are a few other template keywords besides `{PATH}` and `{VALUE}` too. To find out more, details are available [here](#error_messages_MongooseError-messages) | |
| 363 | * | |
| 364 | * ####Asynchronous validation: | |
| 365 | * | |
| 366 | * Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The first argument passed to the validator function is the value being validated. The second argument is a callback function that must called when you finish validating the value and passed either `true` or `false` to communicate either success or failure respectively. | |
| 367 | * | |
| 368 | * schema.path('name').validate(function (value, respond) { | |
| 369 | * doStuff(value, function () { | |
| 370 | * ... | |
| 371 | * respond(false); // validation failed | |
| 372 | * }) | |
| 373 | * }, '{PATH} failed validation.'); | |
| 374 | * | |
| 375 | * You might use asynchronous validators to retreive other documents from the database to validate against or to meet other I/O bound validation needs. | |
| 376 | * | |
| 377 | * Validation occurs `pre('save')` or whenever you manually execute [document#validate](#document_Document-validate). | |
| 378 | * | |
| 379 | * If validation fails during `pre('save')` and no callback was passed to receive the error, an `error` event will be emitted on your Models associated db [connection](#connection_Connection), passing the validation error object along. | |
| 380 | * | |
| 381 | * var conn = mongoose.createConnection(..); | |
| 382 | * conn.on('error', handleError); | |
| 383 | * | |
| 384 | * var Product = conn.model('Product', yourSchema); | |
| 385 | * var dvd = new Product(..); | |
| 386 | * dvd.save(); // emits error on the `conn` above | |
| 387 | * | |
| 388 | * If you desire handling these errors at the Model level, attach an `error` listener to your Model and the event will instead be emitted there. | |
| 389 | * | |
| 390 | * // registering an error listener on the Model lets us handle errors more locally | |
| 391 | * Product.on('error', handleError); | |
| 392 | * | |
| 393 | * @param {RegExp|Function|Object} obj validator | |
| 394 | * @param {String} [errorMsg] optional error message | |
| 395 | * @return {SchemaType} this | |
| 396 | * @api public | |
| 397 | */ | |
| 398 | ||
| 399 | 1 | SchemaType.prototype.validate = function (obj, message) { |
| 400 | 0 | if ('function' == typeof obj || obj && 'RegExp' === obj.constructor.name) { |
| 401 | 0 | if (!message) message = errorMessages.general.default; |
| 402 | 0 | this.validators.push([obj, message, 'user defined']); |
| 403 | 0 | return this; |
| 404 | } | |
| 405 | ||
| 406 | 0 | var i = arguments.length |
| 407 | , arg | |
| 408 | ||
| 409 | 0 | while (i--) { |
| 410 | 0 | arg = arguments[i]; |
| 411 | 0 | if (!(arg && 'Object' == arg.constructor.name)) { |
| 412 | 0 | var msg = 'Invalid validator. Received (' + typeof arg + ') ' |
| 413 | + arg | |
| 414 | + '. See http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate'; | |
| 415 | ||
| 416 | 0 | throw new Error(msg); |
| 417 | } | |
| 418 | 0 | this.validate(arg.validator, arg.msg); |
| 419 | } | |
| 420 | ||
| 421 | 0 | return this; |
| 422 | }; | |
| 423 | ||
| 424 | /** | |
| 425 | * Adds a required validator to this schematype. | |
| 426 | * | |
| 427 | * ####Example: | |
| 428 | * | |
| 429 | * var s = new Schema({ born: { type: Date, required: true }) | |
| 430 | * | |
| 431 | * // or with custom error message | |
| 432 | * | |
| 433 | * var s = new Schema({ born: { type: Date, required: '{PATH} is required!' }) | |
| 434 | * | |
| 435 | * // or through the path API | |
| 436 | * | |
| 437 | * Schema.path('name').required(true); | |
| 438 | * | |
| 439 | * // with custom error messaging | |
| 440 | * | |
| 441 | * Schema.path('name').required(true, 'grrr :( '); | |
| 442 | * | |
| 443 | * | |
| 444 | * @param {Boolean} required enable/disable the validator | |
| 445 | * @param {String} [message] optional custom error message | |
| 446 | * @return {SchemaType} this | |
| 447 | * @see Customized Error Messages #error_messages_MongooseError-messages | |
| 448 | * @api public | |
| 449 | */ | |
| 450 | ||
| 451 | 1 | SchemaType.prototype.required = function (required, message) { |
| 452 | 0 | if (false === required) { |
| 453 | 0 | this.validators = this.validators.filter(function (v) { |
| 454 | 0 | return v[0] != this.requiredValidator; |
| 455 | }, this); | |
| 456 | ||
| 457 | 0 | this.isRequired = false; |
| 458 | 0 | return this; |
| 459 | } | |
| 460 | ||
| 461 | 0 | var self = this; |
| 462 | 0 | this.isRequired = true; |
| 463 | ||
| 464 | 0 | this.requiredValidator = function (v) { |
| 465 | // in here, `this` refers to the validating document. | |
| 466 | // no validation when this path wasn't selected in the query. | |
| 467 | 0 | if ('isSelected' in this && |
| 468 | !this.isSelected(self.path) && | |
| 469 | 0 | !this.isModified(self.path)) return true; |
| 470 | 0 | return self.checkRequired(v, this); |
| 471 | } | |
| 472 | ||
| 473 | 0 | if ('string' == typeof required) { |
| 474 | 0 | message = required; |
| 475 | 0 | required = undefined; |
| 476 | } | |
| 477 | ||
| 478 | 0 | var msg = message || errorMessages.general.required; |
| 479 | 0 | this.validators.push([this.requiredValidator, msg, 'required']); |
| 480 | ||
| 481 | 0 | return this; |
| 482 | }; | |
| 483 | ||
| 484 | /** | |
| 485 | * Gets the default value | |
| 486 | * | |
| 487 | * @param {Object} scope the scope which callback are executed | |
| 488 | * @param {Boolean} init | |
| 489 | * @api private | |
| 490 | */ | |
| 491 | ||
| 492 | 1 | SchemaType.prototype.getDefault = function (scope, init) { |
| 493 | 0 | var ret = 'function' === typeof this.defaultValue |
| 494 | ? this.defaultValue.call(scope) | |
| 495 | : this.defaultValue; | |
| 496 | ||
| 497 | 0 | if (null !== ret && undefined !== ret) { |
| 498 | 0 | return this.cast(ret, scope, init); |
| 499 | } else { | |
| 500 | 0 | return ret; |
| 501 | } | |
| 502 | }; | |
| 503 | ||
| 504 | /** | |
| 505 | * Applies setters | |
| 506 | * | |
| 507 | * @param {Object} value | |
| 508 | * @param {Object} scope | |
| 509 | * @param {Boolean} init | |
| 510 | * @api private | |
| 511 | */ | |
| 512 | ||
| 513 | 1 | SchemaType.prototype.applySetters = function (value, scope, init, priorVal) { |
| 514 | 0 | if (SchemaType._isRef(this, value, scope, init)) { |
| 515 | 0 | return init |
| 516 | ? value | |
| 517 | : this.cast(value, scope, init, priorVal); | |
| 518 | } | |
| 519 | ||
| 520 | 0 | var v = value |
| 521 | , setters = this.setters | |
| 522 | , len = setters.length | |
| 523 | ||
| 524 | 0 | if (!len) { |
| 525 | 0 | if (null === v || undefined === v) return v; |
| 526 | 0 | return this.cast(v, scope, init, priorVal) |
| 527 | } | |
| 528 | ||
| 529 | 0 | while (len--) { |
| 530 | 0 | v = setters[len].call(scope, v, this); |
| 531 | } | |
| 532 | ||
| 533 | 0 | if (null === v || undefined === v) return v; |
| 534 | ||
| 535 | // do not cast until all setters are applied #665 | |
| 536 | 0 | v = this.cast(v, scope, init, priorVal); |
| 537 | ||
| 538 | 0 | return v; |
| 539 | }; | |
| 540 | ||
| 541 | /** | |
| 542 | * Applies getters to a value | |
| 543 | * | |
| 544 | * @param {Object} value | |
| 545 | * @param {Object} scope | |
| 546 | * @api private | |
| 547 | */ | |
| 548 | ||
| 549 | 1 | SchemaType.prototype.applyGetters = function (value, scope) { |
| 550 | 0 | if (SchemaType._isRef(this, value, scope, true)) return value; |
| 551 | ||
| 552 | 0 | var v = value |
| 553 | , getters = this.getters | |
| 554 | , len = getters.length; | |
| 555 | ||
| 556 | 0 | if (!len) { |
| 557 | 0 | return v; |
| 558 | } | |
| 559 | ||
| 560 | 0 | while (len--) { |
| 561 | 0 | v = getters[len].call(scope, v, this); |
| 562 | } | |
| 563 | ||
| 564 | 0 | return v; |
| 565 | }; | |
| 566 | ||
| 567 | /** | |
| 568 | * Sets default `select()` behavior for this path. | |
| 569 | * | |
| 570 | * Set to `true` if this path should always be included in the results, `false` if it should be excluded by default. This setting can be overridden at the query level. | |
| 571 | * | |
| 572 | * ####Example: | |
| 573 | * | |
| 574 | * T = db.model('T', new Schema({ x: { type: String, select: true }})); | |
| 575 | * T.find(..); // field x will always be selected .. | |
| 576 | * // .. unless overridden; | |
| 577 | * T.find().select('-x').exec(callback); | |
| 578 | * | |
| 579 | * @param {Boolean} val | |
| 580 | * @return {SchemaType} this | |
| 581 | * @api public | |
| 582 | */ | |
| 583 | ||
| 584 | 1 | SchemaType.prototype.select = function select (val) { |
| 585 | 0 | this.selected = !! val; |
| 586 | 0 | return this; |
| 587 | } | |
| 588 | ||
| 589 | /** | |
| 590 | * Performs a validation of `value` using the validators declared for this SchemaType. | |
| 591 | * | |
| 592 | * @param {any} value | |
| 593 | * @param {Function} callback | |
| 594 | * @param {Object} scope | |
| 595 | * @api private | |
| 596 | */ | |
| 597 | ||
| 598 | 1 | SchemaType.prototype.doValidate = function (value, fn, scope) { |
| 599 | 0 | var err = false |
| 600 | , path = this.path | |
| 601 | , count = this.validators.length; | |
| 602 | ||
| 603 | 0 | if (!count) return fn(null); |
| 604 | ||
| 605 | 0 | function validate (ok, message, type, val) { |
| 606 | 0 | if (err) return; |
| 607 | 0 | if (ok === undefined || ok) { |
| 608 | 0 | --count || fn(null); |
| 609 | } else { | |
| 610 | 0 | fn(err = new ValidatorError(path, message, type, val)); |
| 611 | } | |
| 612 | } | |
| 613 | ||
| 614 | 0 | this.validators.forEach(function (v) { |
| 615 | 0 | var validator = v[0] |
| 616 | , message = v[1] | |
| 617 | , type = v[2]; | |
| 618 | ||
| 619 | 0 | if (validator instanceof RegExp) { |
| 620 | 0 | validate(validator.test(value), message, type, value); |
| 621 | 0 | } else if ('function' === typeof validator) { |
| 622 | 0 | if (2 === validator.length) { |
| 623 | 0 | validator.call(scope, value, function (ok) { |
| 624 | 0 | validate(ok, message, type, value); |
| 625 | }); | |
| 626 | } else { | |
| 627 | 0 | validate(validator.call(scope, value), message, type, value); |
| 628 | } | |
| 629 | } | |
| 630 | }); | |
| 631 | }; | |
| 632 | ||
| 633 | /** | |
| 634 | * Determines if value is a valid Reference. | |
| 635 | * | |
| 636 | * @param {SchemaType} self | |
| 637 | * @param {Object} value | |
| 638 | * @param {Document} doc | |
| 639 | * @param {Boolean} init | |
| 640 | * @return {Boolean} | |
| 641 | * @api private | |
| 642 | */ | |
| 643 | ||
| 644 | 1 | SchemaType._isRef = function (self, value, doc, init) { |
| 645 | // fast path | |
| 646 | 0 | var ref = init && self.options && self.options.ref; |
| 647 | ||
| 648 | 0 | if (!ref && doc && doc.$__fullPath) { |
| 649 | // checks for | |
| 650 | // - this populated with adhoc model and no ref was set in schema OR | |
| 651 | // - setting / pushing values after population | |
| 652 | 0 | var path = doc.$__fullPath(self.path); |
| 653 | 0 | var owner = doc.ownerDocument ? doc.ownerDocument() : doc; |
| 654 | 0 | ref = owner.populated(path); |
| 655 | } | |
| 656 | ||
| 657 | 0 | if (ref) { |
| 658 | 0 | if (null == value) return true; |
| 659 | 0 | if (!Buffer.isBuffer(value) && // buffers are objects too |
| 660 | 'Binary' != value._bsontype // raw binary value from the db | |
| 661 | && utils.isObject(value) // might have deselected _id in population query | |
| 662 | ) { | |
| 663 | 0 | return true; |
| 664 | } | |
| 665 | } | |
| 666 | ||
| 667 | 0 | return false; |
| 668 | } | |
| 669 | ||
| 670 | /*! | |
| 671 | * Module exports. | |
| 672 | */ | |
| 673 | ||
| 674 | 1 | module.exports = exports = SchemaType; |
| 675 | ||
| 676 | 1 | exports.CastError = CastError; |
| 677 | ||
| 678 | 1 | exports.ValidatorError = ValidatorError; |
| 679 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('./utils'); |
| 7 | ||
| 8 | /*! | |
| 9 | * StateMachine represents a minimal `interface` for the | |
| 10 | * constructors it builds via StateMachine.ctor(...). | |
| 11 | * | |
| 12 | * @api private | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | var StateMachine = module.exports = exports = function StateMachine () { |
| 16 | 0 | this.paths = {}; |
| 17 | 0 | this.states = {}; |
| 18 | } | |
| 19 | ||
| 20 | /*! | |
| 21 | * StateMachine.ctor('state1', 'state2', ...) | |
| 22 | * A factory method for subclassing StateMachine. | |
| 23 | * The arguments are a list of states. For each state, | |
| 24 | * the constructor's prototype gets state transition | |
| 25 | * methods named after each state. These transition methods | |
| 26 | * place their path argument into the given state. | |
| 27 | * | |
| 28 | * @param {String} state | |
| 29 | * @param {String} [state] | |
| 30 | * @return {Function} subclass constructor | |
| 31 | * @private | |
| 32 | */ | |
| 33 | ||
| 34 | 1 | StateMachine.ctor = function () { |
| 35 | 1 | var states = utils.args(arguments); |
| 36 | ||
| 37 | 1 | var ctor = function () { |
| 38 | 0 | StateMachine.apply(this, arguments); |
| 39 | 0 | this.stateNames = states; |
| 40 | ||
| 41 | 0 | var i = states.length |
| 42 | , state; | |
| 43 | ||
| 44 | 0 | while (i--) { |
| 45 | 0 | state = states[i]; |
| 46 | 0 | this.states[state] = {}; |
| 47 | } | |
| 48 | }; | |
| 49 | ||
| 50 | 1 | ctor.prototype.__proto__ = StateMachine.prototype; |
| 51 | ||
| 52 | 1 | states.forEach(function (state) { |
| 53 | // Changes the `path`'s state to `state`. | |
| 54 | 4 | ctor.prototype[state] = function (path) { |
| 55 | 0 | this._changeState(path, state); |
| 56 | } | |
| 57 | }); | |
| 58 | ||
| 59 | 1 | return ctor; |
| 60 | }; | |
| 61 | ||
| 62 | /*! | |
| 63 | * This function is wrapped by the state change functions: | |
| 64 | * | |
| 65 | * - `require(path)` | |
| 66 | * - `modify(path)` | |
| 67 | * - `init(path)` | |
| 68 | * | |
| 69 | * @api private | |
| 70 | */ | |
| 71 | ||
| 72 | 1 | StateMachine.prototype._changeState = function _changeState (path, nextState) { |
| 73 | 0 | var prevBucket = this.states[this.paths[path]]; |
| 74 | 0 | if (prevBucket) delete prevBucket[path]; |
| 75 | ||
| 76 | 0 | this.paths[path] = nextState; |
| 77 | 0 | this.states[nextState][path] = true; |
| 78 | } | |
| 79 | ||
| 80 | /*! | |
| 81 | * ignore | |
| 82 | */ | |
| 83 | ||
| 84 | 1 | StateMachine.prototype.clear = function clear (state) { |
| 85 | 0 | var keys = Object.keys(this.states[state]) |
| 86 | , i = keys.length | |
| 87 | , path | |
| 88 | ||
| 89 | 0 | while (i--) { |
| 90 | 0 | path = keys[i]; |
| 91 | 0 | delete this.states[state][path]; |
| 92 | 0 | delete this.paths[path]; |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | /*! | |
| 97 | * Checks to see if at least one path is in the states passed in via `arguments` | |
| 98 | * e.g., this.some('required', 'inited') | |
| 99 | * | |
| 100 | * @param {String} state that we want to check for. | |
| 101 | * @private | |
| 102 | */ | |
| 103 | ||
| 104 | 1 | StateMachine.prototype.some = function some () { |
| 105 | 0 | var self = this; |
| 106 | 0 | var what = arguments.length ? arguments : this.stateNames; |
| 107 | 0 | return Array.prototype.some.call(what, function (state) { |
| 108 | 0 | return Object.keys(self.states[state]).length; |
| 109 | }); | |
| 110 | } | |
| 111 | ||
| 112 | /*! | |
| 113 | * This function builds the functions that get assigned to `forEach` and `map`, | |
| 114 | * since both of those methods share a lot of the same logic. | |
| 115 | * | |
| 116 | * @param {String} iterMethod is either 'forEach' or 'map' | |
| 117 | * @return {Function} | |
| 118 | * @api private | |
| 119 | */ | |
| 120 | ||
| 121 | 1 | StateMachine.prototype._iter = function _iter (iterMethod) { |
| 122 | 0 | return function () { |
| 123 | 0 | var numArgs = arguments.length |
| 124 | , states = utils.args(arguments, 0, numArgs-1) | |
| 125 | , callback = arguments[numArgs-1]; | |
| 126 | ||
| 127 | 0 | if (!states.length) states = this.stateNames; |
| 128 | ||
| 129 | 0 | var self = this; |
| 130 | ||
| 131 | 0 | var paths = states.reduce(function (paths, state) { |
| 132 | 0 | return paths.concat(Object.keys(self.states[state])); |
| 133 | }, []); | |
| 134 | ||
| 135 | 0 | return paths[iterMethod](function (path, i, paths) { |
| 136 | 0 | return callback(path, i, paths); |
| 137 | }); | |
| 138 | }; | |
| 139 | } | |
| 140 | ||
| 141 | /*! | |
| 142 | * Iterates over the paths that belong to one of the parameter states. | |
| 143 | * | |
| 144 | * The function profile can look like: | |
| 145 | * this.forEach(state1, fn); // iterates over all paths in state1 | |
| 146 | * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 | |
| 147 | * this.forEach(fn); // iterates over all paths in all states | |
| 148 | * | |
| 149 | * @param {String} [state] | |
| 150 | * @param {String} [state] | |
| 151 | * @param {Function} callback | |
| 152 | * @private | |
| 153 | */ | |
| 154 | ||
| 155 | 1 | StateMachine.prototype.forEach = function forEach () { |
| 156 | 0 | this.forEach = this._iter('forEach'); |
| 157 | 0 | return this.forEach.apply(this, arguments); |
| 158 | } | |
| 159 | ||
| 160 | /*! | |
| 161 | * Maps over the paths that belong to one of the parameter states. | |
| 162 | * | |
| 163 | * The function profile can look like: | |
| 164 | * this.forEach(state1, fn); // iterates over all paths in state1 | |
| 165 | * this.forEach(state1, state2, fn); // iterates over all paths in state1 or state2 | |
| 166 | * this.forEach(fn); // iterates over all paths in all states | |
| 167 | * | |
| 168 | * @param {String} [state] | |
| 169 | * @param {String} [state] | |
| 170 | * @param {Function} callback | |
| 171 | * @return {Array} | |
| 172 | * @private | |
| 173 | */ | |
| 174 | ||
| 175 | 1 | StateMachine.prototype.map = function map () { |
| 176 | 0 | this.map = this._iter('map'); |
| 177 | 0 | return this.map.apply(this, arguments); |
| 178 | } | |
| 179 | ||
| 180 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var EmbeddedDocument = require('./embedded'); |
| 7 | 1 | var Document = require('../document'); |
| 8 | 1 | var ObjectId = require('./objectid'); |
| 9 | 1 | var utils = require('../utils'); |
| 10 | 1 | var isMongooseObject = utils.isMongooseObject; |
| 11 | ||
| 12 | /** | |
| 13 | * Mongoose Array constructor. | |
| 14 | * | |
| 15 | * ####NOTE: | |
| 16 | * | |
| 17 | * _Values always have to be passed to the constructor to initialize, otherwise `MongooseArray#push` will mark the array as modified._ | |
| 18 | * | |
| 19 | * @param {Array} values | |
| 20 | * @param {String} path | |
| 21 | * @param {Document} doc parent document | |
| 22 | * @api private | |
| 23 | * @inherits Array | |
| 24 | * @see http://bit.ly/f6CnZU | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | function MongooseArray (values, path, doc) { |
| 28 | 0 | var arr = []; |
| 29 | 0 | arr.push.apply(arr, values); |
| 30 | 0 | arr.__proto__ = MongooseArray.prototype; |
| 31 | ||
| 32 | 0 | arr._atomics = {}; |
| 33 | 0 | arr.validators = []; |
| 34 | 0 | arr._path = path; |
| 35 | ||
| 36 | 0 | if (doc) { |
| 37 | 0 | arr._parent = doc; |
| 38 | 0 | arr._schema = doc.schema.path(path); |
| 39 | } | |
| 40 | ||
| 41 | 0 | return arr; |
| 42 | }; | |
| 43 | ||
| 44 | /*! | |
| 45 | * Inherit from Array | |
| 46 | */ | |
| 47 | ||
| 48 | 1 | MongooseArray.prototype = new Array; |
| 49 | ||
| 50 | /** | |
| 51 | * Stores a queue of atomic operations to perform | |
| 52 | * | |
| 53 | * @property _atomics | |
| 54 | * @api private | |
| 55 | */ | |
| 56 | ||
| 57 | 1 | MongooseArray.prototype._atomics; |
| 58 | ||
| 59 | /** | |
| 60 | * Parent owner document | |
| 61 | * | |
| 62 | * @property _parent | |
| 63 | * @api private | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | MongooseArray.prototype._parent; |
| 67 | ||
| 68 | /** | |
| 69 | * Casts a member based on this arrays schema. | |
| 70 | * | |
| 71 | * @param {any} value | |
| 72 | * @return value the casted value | |
| 73 | * @api private | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | MongooseArray.prototype._cast = function (value) { |
| 77 | 0 | var owner = this._owner; |
| 78 | 0 | var populated = false; |
| 79 | 0 | var Model; |
| 80 | ||
| 81 | 0 | if (this._parent) { |
| 82 | // if a populated array, we must cast to the same model | |
| 83 | // instance as specified in the original query. | |
| 84 | 0 | if (!owner) { |
| 85 | 0 | owner = this._owner = this._parent.ownerDocument |
| 86 | ? this._parent.ownerDocument() | |
| 87 | : this._parent; | |
| 88 | } | |
| 89 | ||
| 90 | 0 | populated = owner.populated(this._path, true); |
| 91 | } | |
| 92 | ||
| 93 | 0 | if (populated && null != value) { |
| 94 | // cast to the populated Models schema | |
| 95 | 0 | var Model = populated.options.model; |
| 96 | ||
| 97 | // only objects are permitted so we can safely assume that | |
| 98 | // non-objects are to be interpreted as _id | |
| 99 | 0 | if (Buffer.isBuffer(value) || |
| 100 | value instanceof ObjectId || !utils.isObject(value)) { | |
| 101 | 0 | value = { _id: value }; |
| 102 | } | |
| 103 | ||
| 104 | 0 | value = new Model(value); |
| 105 | 0 | return this._schema.caster.cast(value, this._parent, true) |
| 106 | } | |
| 107 | ||
| 108 | 0 | return this._schema.caster.cast(value, this._parent, false) |
| 109 | } | |
| 110 | ||
| 111 | /** | |
| 112 | * Marks this array as modified. | |
| 113 | * | |
| 114 | * If it bubbles up from an embedded document change, then it takes the following arguments (otherwise, takes 0 arguments) | |
| 115 | * | |
| 116 | * @param {EmbeddedDocument} embeddedDoc the embedded doc that invoked this method on the Array | |
| 117 | * @param {String} embeddedPath the path which changed in the embeddedDoc | |
| 118 | * @api private | |
| 119 | */ | |
| 120 | ||
| 121 | 1 | MongooseArray.prototype._markModified = function (elem, embeddedPath) { |
| 122 | 0 | var parent = this._parent |
| 123 | , dirtyPath; | |
| 124 | ||
| 125 | 0 | if (parent) { |
| 126 | 0 | dirtyPath = this._path; |
| 127 | ||
| 128 | 0 | if (arguments.length) { |
| 129 | 0 | if (null != embeddedPath) { |
| 130 | // an embedded doc bubbled up the change | |
| 131 | 0 | dirtyPath = dirtyPath + '.' + this.indexOf(elem) + '.' + embeddedPath; |
| 132 | } else { | |
| 133 | // directly set an index | |
| 134 | 0 | dirtyPath = dirtyPath + '.' + elem; |
| 135 | } | |
| 136 | } | |
| 137 | 0 | parent.markModified(dirtyPath); |
| 138 | } | |
| 139 | ||
| 140 | 0 | return this; |
| 141 | }; | |
| 142 | ||
| 143 | /** | |
| 144 | * Register an atomic operation with the parent. | |
| 145 | * | |
| 146 | * @param {Array} op operation | |
| 147 | * @param {any} val | |
| 148 | * @api private | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | MongooseArray.prototype._registerAtomic = function (op, val) { |
| 152 | 0 | if ('$set' == op) { |
| 153 | // $set takes precedence over all other ops. | |
| 154 | // mark entire array modified. | |
| 155 | 0 | this._atomics = { $set: val }; |
| 156 | 0 | return this; |
| 157 | } | |
| 158 | ||
| 159 | 0 | var atomics = this._atomics; |
| 160 | ||
| 161 | // reset pop/shift after save | |
| 162 | 0 | if ('$pop' == op && !('$pop' in atomics)) { |
| 163 | 0 | var self = this; |
| 164 | 0 | this._parent.once('save', function () { |
| 165 | 0 | self._popped = self._shifted = null; |
| 166 | }); | |
| 167 | } | |
| 168 | ||
| 169 | // check for impossible $atomic combos (Mongo denies more than one | |
| 170 | // $atomic op on a single path | |
| 171 | 0 | if (this._atomics.$set || |
| 172 | Object.keys(atomics).length && !(op in atomics)) { | |
| 173 | // a different op was previously registered. | |
| 174 | // save the entire thing. | |
| 175 | 0 | this._atomics = { $set: this }; |
| 176 | 0 | return this; |
| 177 | } | |
| 178 | ||
| 179 | 0 | if (op === '$pullAll' || op === '$pushAll' || op === '$addToSet') { |
| 180 | 0 | atomics[op] || (atomics[op] = []); |
| 181 | 0 | atomics[op] = atomics[op].concat(val); |
| 182 | 0 | } else if (op === '$pullDocs') { |
| 183 | 0 | var pullOp = atomics['$pull'] || (atomics['$pull'] = {}) |
| 184 | , selector = pullOp['_id'] || (pullOp['_id'] = {'$in' : [] }); | |
| 185 | 0 | selector['$in'] = selector['$in'].concat(val); |
| 186 | } else { | |
| 187 | 0 | atomics[op] = val; |
| 188 | } | |
| 189 | ||
| 190 | 0 | return this; |
| 191 | }; | |
| 192 | ||
| 193 | /** | |
| 194 | * Depopulates stored atomic operation values as necessary for direct insertion to MongoDB. | |
| 195 | * | |
| 196 | * If no atomics exist, we return all array values after conversion. | |
| 197 | * | |
| 198 | * @return {Array} | |
| 199 | * @method $__getAtomics | |
| 200 | * @memberOf MongooseArray | |
| 201 | * @api private | |
| 202 | */ | |
| 203 | ||
| 204 | 1 | MongooseArray.prototype.$__getAtomics = function () { |
| 205 | 0 | var ret = []; |
| 206 | 0 | var keys = Object.keys(this._atomics); |
| 207 | 0 | var i = keys.length; |
| 208 | ||
| 209 | 0 | if (0 === i) { |
| 210 | 0 | ret[0] = ['$set', this.toObject({ depopulate: 1 })]; |
| 211 | 0 | return ret; |
| 212 | } | |
| 213 | ||
| 214 | 0 | while (i--) { |
| 215 | 0 | var op = keys[i]; |
| 216 | 0 | var val = this._atomics[op]; |
| 217 | ||
| 218 | // the atomic values which are arrays are not MongooseArrays. we | |
| 219 | // need to convert their elements as if they were MongooseArrays | |
| 220 | // to handle populated arrays versus DocumentArrays properly. | |
| 221 | 0 | if (isMongooseObject(val)) { |
| 222 | 0 | val = val.toObject({ depopulate: 1 }); |
| 223 | 0 | } else if (Array.isArray(val)) { |
| 224 | 0 | val = this.toObject.call(val, { depopulate: 1 }); |
| 225 | 0 | } else if (val.valueOf) { |
| 226 | 0 | val = val.valueOf(); |
| 227 | } | |
| 228 | ||
| 229 | 0 | if ('$addToSet' == op) { |
| 230 | 0 | val = { $each: val } |
| 231 | } | |
| 232 | ||
| 233 | 0 | ret.push([op, val]); |
| 234 | } | |
| 235 | ||
| 236 | 0 | return ret; |
| 237 | } | |
| 238 | ||
| 239 | /** | |
| 240 | * Returns the number of pending atomic operations to send to the db for this array. | |
| 241 | * | |
| 242 | * @api private | |
| 243 | * @return {Number} | |
| 244 | */ | |
| 245 | ||
| 246 | 1 | MongooseArray.prototype.hasAtomics = function hasAtomics () { |
| 247 | 0 | if (!(this._atomics && 'Object' === this._atomics.constructor.name)) { |
| 248 | 0 | return 0; |
| 249 | } | |
| 250 | ||
| 251 | 0 | return Object.keys(this._atomics).length; |
| 252 | } | |
| 253 | ||
| 254 | /** | |
| 255 | * Wraps [`Array#push`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/push) with proper change tracking. | |
| 256 | * | |
| 257 | * @param {Object} [args...] | |
| 258 | * @api public | |
| 259 | */ | |
| 260 | ||
| 261 | 1 | MongooseArray.prototype.push = function () { |
| 262 | 0 | var values = [].map.call(arguments, this._cast, this) |
| 263 | , ret = [].push.apply(this, values); | |
| 264 | ||
| 265 | // $pushAll might be fibbed (could be $push). But it makes it easier to | |
| 266 | // handle what could have been $push, $pushAll combos | |
| 267 | 0 | this._registerAtomic('$pushAll', values); |
| 268 | 0 | this._markModified(); |
| 269 | 0 | return ret; |
| 270 | }; | |
| 271 | ||
| 272 | /** | |
| 273 | * Pushes items to the array non-atomically. | |
| 274 | * | |
| 275 | * ####NOTE: | |
| 276 | * | |
| 277 | * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 278 | * | |
| 279 | * @param {any} [args...] | |
| 280 | * @api public | |
| 281 | */ | |
| 282 | ||
| 283 | 1 | MongooseArray.prototype.nonAtomicPush = function () { |
| 284 | 0 | var values = [].map.call(arguments, this._cast, this) |
| 285 | , ret = [].push.apply(this, values); | |
| 286 | 0 | this._registerAtomic('$set', this); |
| 287 | 0 | this._markModified(); |
| 288 | 0 | return ret; |
| 289 | }; | |
| 290 | ||
| 291 | /** | |
| 292 | * Pops the array atomically at most one time per document `save()`. | |
| 293 | * | |
| 294 | * #### NOTE: | |
| 295 | * | |
| 296 | * _Calling this mulitple times on an array before saving sends the same command as calling it once._ | |
| 297 | * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ | |
| 298 | * | |
| 299 | * doc.array = [1,2,3]; | |
| 300 | * | |
| 301 | * var popped = doc.array.$pop(); | |
| 302 | * console.log(popped); // 3 | |
| 303 | * console.log(doc.array); // [1,2] | |
| 304 | * | |
| 305 | * // no affect | |
| 306 | * popped = doc.array.$pop(); | |
| 307 | * console.log(doc.array); // [1,2] | |
| 308 | * | |
| 309 | * doc.save(function (err) { | |
| 310 | * if (err) return handleError(err); | |
| 311 | * | |
| 312 | * // we saved, now $pop works again | |
| 313 | * popped = doc.array.$pop(); | |
| 314 | * console.log(popped); // 2 | |
| 315 | * console.log(doc.array); // [1] | |
| 316 | * }) | |
| 317 | * | |
| 318 | * @api public | |
| 319 | * @method $pop | |
| 320 | * @memberOf MongooseArray | |
| 321 | * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop | |
| 322 | */ | |
| 323 | ||
| 324 | 1 | MongooseArray.prototype.$pop = function () { |
| 325 | 0 | this._registerAtomic('$pop', 1); |
| 326 | 0 | this._markModified(); |
| 327 | ||
| 328 | // only allow popping once | |
| 329 | 0 | if (this._popped) return; |
| 330 | 0 | this._popped = true; |
| 331 | ||
| 332 | 0 | return [].pop.call(this); |
| 333 | }; | |
| 334 | ||
| 335 | /** | |
| 336 | * Wraps [`Array#pop`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/pop) with proper change tracking. | |
| 337 | * | |
| 338 | * ####Note: | |
| 339 | * | |
| 340 | * _marks the entire array as modified which will pass the entire thing to $set potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 341 | * | |
| 342 | * @see MongooseArray#$pop #types_array_MongooseArray-%24pop | |
| 343 | * @api public | |
| 344 | */ | |
| 345 | ||
| 346 | 1 | MongooseArray.prototype.pop = function () { |
| 347 | 0 | var ret = [].pop.call(this); |
| 348 | 0 | this._registerAtomic('$set', this); |
| 349 | 0 | this._markModified(); |
| 350 | 0 | return ret; |
| 351 | }; | |
| 352 | ||
| 353 | /** | |
| 354 | * Atomically shifts the array at most one time per document `save()`. | |
| 355 | * | |
| 356 | * ####NOTE: | |
| 357 | * | |
| 358 | * _Calling this mulitple times on an array before saving sends the same command as calling it once._ | |
| 359 | * _This update is implemented using the MongoDB [$pop](http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop) method which enforces this restriction._ | |
| 360 | * | |
| 361 | * doc.array = [1,2,3]; | |
| 362 | * | |
| 363 | * var shifted = doc.array.$shift(); | |
| 364 | * console.log(shifted); // 1 | |
| 365 | * console.log(doc.array); // [2,3] | |
| 366 | * | |
| 367 | * // no affect | |
| 368 | * shifted = doc.array.$shift(); | |
| 369 | * console.log(doc.array); // [2,3] | |
| 370 | * | |
| 371 | * doc.save(function (err) { | |
| 372 | * if (err) return handleError(err); | |
| 373 | * | |
| 374 | * // we saved, now $shift works again | |
| 375 | * shifted = doc.array.$shift(); | |
| 376 | * console.log(shifted ); // 2 | |
| 377 | * console.log(doc.array); // [3] | |
| 378 | * }) | |
| 379 | * | |
| 380 | * @api public | |
| 381 | * @memberOf MongooseArray | |
| 382 | * @method $shift | |
| 383 | * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pop | |
| 384 | */ | |
| 385 | ||
| 386 | 1 | MongooseArray.prototype.$shift = function $shift () { |
| 387 | 0 | this._registerAtomic('$pop', -1); |
| 388 | 0 | this._markModified(); |
| 389 | ||
| 390 | // only allow shifting once | |
| 391 | 0 | if (this._shifted) return; |
| 392 | 0 | this._shifted = true; |
| 393 | ||
| 394 | 0 | return [].shift.call(this); |
| 395 | }; | |
| 396 | ||
| 397 | /** | |
| 398 | * Wraps [`Array#shift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. | |
| 399 | * | |
| 400 | * ####Example: | |
| 401 | * | |
| 402 | * doc.array = [2,3]; | |
| 403 | * var res = doc.array.shift(); | |
| 404 | * console.log(res) // 2 | |
| 405 | * console.log(doc.array) // [3] | |
| 406 | * | |
| 407 | * ####Note: | |
| 408 | * | |
| 409 | * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 410 | * | |
| 411 | * @api public | |
| 412 | */ | |
| 413 | ||
| 414 | 1 | MongooseArray.prototype.shift = function () { |
| 415 | 0 | var ret = [].shift.call(this); |
| 416 | 0 | this._registerAtomic('$set', this); |
| 417 | 0 | this._markModified(); |
| 418 | 0 | return ret; |
| 419 | }; | |
| 420 | ||
| 421 | /** | |
| 422 | * Pulls items from the array atomically. | |
| 423 | * | |
| 424 | * ####Examples: | |
| 425 | * | |
| 426 | * doc.array.pull(ObjectId) | |
| 427 | * doc.array.pull({ _id: 'someId' }) | |
| 428 | * doc.array.pull(36) | |
| 429 | * doc.array.pull('tag 1', 'tag 2') | |
| 430 | * | |
| 431 | * To remove a document from a subdocument array we may pass an object with a matching `_id`. | |
| 432 | * | |
| 433 | * doc.subdocs.push({ _id: 4815162342 }) | |
| 434 | * doc.subdocs.pull({ _id: 4815162342 }) // removed | |
| 435 | * | |
| 436 | * Or we may passing the _id directly and let mongoose take care of it. | |
| 437 | * | |
| 438 | * doc.subdocs.push({ _id: 4815162342 }) | |
| 439 | * doc.subdocs.pull(4815162342); // works | |
| 440 | * | |
| 441 | * @param {any} [args...] | |
| 442 | * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull | |
| 443 | * @api public | |
| 444 | */ | |
| 445 | ||
| 446 | 1 | MongooseArray.prototype.pull = function () { |
| 447 | 0 | var values = [].map.call(arguments, this._cast, this) |
| 448 | , cur = this._parent.get(this._path) | |
| 449 | , i = cur.length | |
| 450 | , mem; | |
| 451 | ||
| 452 | 0 | while (i--) { |
| 453 | 0 | mem = cur[i]; |
| 454 | 0 | if (mem instanceof EmbeddedDocument) { |
| 455 | 0 | if (values.some(function (v) { return v.equals(mem); } )) { |
| 456 | 0 | [].splice.call(cur, i, 1); |
| 457 | } | |
| 458 | 0 | } else if (~cur.indexOf.call(values, mem)) { |
| 459 | 0 | [].splice.call(cur, i, 1); |
| 460 | } | |
| 461 | } | |
| 462 | ||
| 463 | 0 | if (values[0] instanceof EmbeddedDocument) { |
| 464 | 0 | this._registerAtomic('$pullDocs', values.map( function (v) { return v._id; } )); |
| 465 | } else { | |
| 466 | 0 | this._registerAtomic('$pullAll', values); |
| 467 | } | |
| 468 | ||
| 469 | 0 | this._markModified(); |
| 470 | 0 | return this; |
| 471 | }; | |
| 472 | ||
| 473 | /** | |
| 474 | * Alias of [pull](#types_array_MongooseArray-pull) | |
| 475 | * | |
| 476 | * @see MongooseArray#pull #types_array_MongooseArray-pull | |
| 477 | * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull | |
| 478 | * @api public | |
| 479 | * @memberOf MongooseArray | |
| 480 | * @method remove | |
| 481 | */ | |
| 482 | ||
| 483 | 1 | MongooseArray.prototype.remove = MongooseArray.prototype.pull; |
| 484 | ||
| 485 | /** | |
| 486 | * Wraps [`Array#splice`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice) with proper change tracking and casting. | |
| 487 | * | |
| 488 | * ####Note: | |
| 489 | * | |
| 490 | * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 491 | * | |
| 492 | * @api public | |
| 493 | */ | |
| 494 | ||
| 495 | 1 | MongooseArray.prototype.splice = function splice () { |
| 496 | 0 | var ret, vals, i; |
| 497 | ||
| 498 | 0 | if (arguments.length) { |
| 499 | 0 | vals = []; |
| 500 | 0 | for (i = 0; i < arguments.length; ++i) { |
| 501 | 0 | vals[i] = i < 2 |
| 502 | ? arguments[i] | |
| 503 | : this._cast(arguments[i]); | |
| 504 | } | |
| 505 | 0 | ret = [].splice.apply(this, vals); |
| 506 | 0 | this._registerAtomic('$set', this); |
| 507 | 0 | this._markModified(); |
| 508 | } | |
| 509 | ||
| 510 | 0 | return ret; |
| 511 | } | |
| 512 | ||
| 513 | /** | |
| 514 | * Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. | |
| 515 | * | |
| 516 | * ####Note: | |
| 517 | * | |
| 518 | * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 519 | * | |
| 520 | * @api public | |
| 521 | */ | |
| 522 | ||
| 523 | 1 | MongooseArray.prototype.unshift = function () { |
| 524 | 0 | var values = [].map.call(arguments, this._cast, this); |
| 525 | 0 | [].unshift.apply(this, values); |
| 526 | 0 | this._registerAtomic('$set', this); |
| 527 | 0 | this._markModified(); |
| 528 | 0 | return this.length; |
| 529 | }; | |
| 530 | ||
| 531 | /** | |
| 532 | * Wraps [`Array#sort`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort) with proper change tracking. | |
| 533 | * | |
| 534 | * ####NOTE: | |
| 535 | * | |
| 536 | * _marks the entire array as modified, which if saved, will store it as a `$set` operation, potentially overwritting any changes that happen between when you retrieved the object and when you save it._ | |
| 537 | * | |
| 538 | * @api public | |
| 539 | */ | |
| 540 | ||
| 541 | 1 | MongooseArray.prototype.sort = function () { |
| 542 | 0 | var ret = [].sort.apply(this, arguments); |
| 543 | 0 | this._registerAtomic('$set', this); |
| 544 | 0 | this._markModified(); |
| 545 | 0 | return ret; |
| 546 | } | |
| 547 | ||
| 548 | /** | |
| 549 | * Adds values to the array if not already present. | |
| 550 | * | |
| 551 | * ####Example: | |
| 552 | * | |
| 553 | * console.log(doc.array) // [2,3,4] | |
| 554 | * var added = doc.array.addToSet(4,5); | |
| 555 | * console.log(doc.array) // [2,3,4,5] | |
| 556 | * console.log(added) // [5] | |
| 557 | * | |
| 558 | * @param {any} [args...] | |
| 559 | * @return {Array} the values that were added | |
| 560 | * @api public | |
| 561 | */ | |
| 562 | ||
| 563 | 1 | MongooseArray.prototype.addToSet = function addToSet () { |
| 564 | 0 | var values = [].map.call(arguments, this._cast, this) |
| 565 | , added = [] | |
| 566 | , type = values[0] instanceof EmbeddedDocument ? 'doc' : | |
| 567 | values[0] instanceof Date ? 'date' : | |
| 568 | ''; | |
| 569 | ||
| 570 | 0 | values.forEach(function (v) { |
| 571 | 0 | var found; |
| 572 | 0 | switch (type) { |
| 573 | case 'doc': | |
| 574 | 0 | found = this.some(function(doc){ return doc.equals(v) }); |
| 575 | 0 | break; |
| 576 | case 'date': | |
| 577 | 0 | var val = +v; |
| 578 | 0 | found = this.some(function(d){ return +d === val }); |
| 579 | 0 | break; |
| 580 | default: | |
| 581 | 0 | found = ~this.indexOf(v); |
| 582 | } | |
| 583 | ||
| 584 | 0 | if (!found) { |
| 585 | 0 | [].push.call(this, v); |
| 586 | 0 | this._registerAtomic('$addToSet', v); |
| 587 | 0 | this._markModified(); |
| 588 | 0 | [].push.call(added, v); |
| 589 | } | |
| 590 | }, this); | |
| 591 | ||
| 592 | 0 | return added; |
| 593 | }; | |
| 594 | ||
| 595 | /** | |
| 596 | * Sets the casted `val` at index `i` and marks the array modified. | |
| 597 | * | |
| 598 | * ####Example: | |
| 599 | * | |
| 600 | * // given documents based on the following | |
| 601 | * var Doc = mongoose.model('Doc', new Schema({ array: [Number] })); | |
| 602 | * | |
| 603 | * var doc = new Doc({ array: [2,3,4] }) | |
| 604 | * | |
| 605 | * console.log(doc.array) // [2,3,4] | |
| 606 | * | |
| 607 | * doc.array.set(1,"5"); | |
| 608 | * console.log(doc.array); // [2,5,4] // properly cast to number | |
| 609 | * doc.save() // the change is saved | |
| 610 | * | |
| 611 | * // VS not using array#set | |
| 612 | * doc.array[1] = "5"; | |
| 613 | * console.log(doc.array); // [2,"5",4] // no casting | |
| 614 | * doc.save() // change is not saved | |
| 615 | * | |
| 616 | * @return {Array} this | |
| 617 | * @api public | |
| 618 | */ | |
| 619 | ||
| 620 | 1 | MongooseArray.prototype.set = function set (i, val) { |
| 621 | 0 | this[i] = this._cast(val); |
| 622 | 0 | this._markModified(i); |
| 623 | 0 | return this; |
| 624 | } | |
| 625 | ||
| 626 | /** | |
| 627 | * Returns a native js Array. | |
| 628 | * | |
| 629 | * @param {Object} options | |
| 630 | * @return {Array} | |
| 631 | * @api public | |
| 632 | */ | |
| 633 | ||
| 634 | 1 | MongooseArray.prototype.toObject = function (options) { |
| 635 | 0 | if (options && options.depopulate) { |
| 636 | 0 | return this.map(function (doc) { |
| 637 | 0 | return doc instanceof Document |
| 638 | ? doc.toObject(options) | |
| 639 | : doc | |
| 640 | }); | |
| 641 | } | |
| 642 | ||
| 643 | 0 | return this.slice(); |
| 644 | } | |
| 645 | ||
| 646 | /** | |
| 647 | * Helper for console.log | |
| 648 | * | |
| 649 | * @api public | |
| 650 | */ | |
| 651 | ||
| 652 | 1 | MongooseArray.prototype.inspect = function () { |
| 653 | 0 | return '[' + this.map(function (doc) { |
| 654 | 0 | return ' ' + doc; |
| 655 | }) + ' ]'; | |
| 656 | }; | |
| 657 | ||
| 658 | /** | |
| 659 | * Return the index of `obj` or `-1` if not found. | |
| 660 | * | |
| 661 | * @param {Object} obj the item to look for | |
| 662 | * @return {Number} | |
| 663 | * @api public | |
| 664 | */ | |
| 665 | ||
| 666 | 1 | MongooseArray.prototype.indexOf = function indexOf (obj) { |
| 667 | 0 | if (obj instanceof ObjectId) obj = obj.toString(); |
| 668 | 0 | for (var i = 0, len = this.length; i < len; ++i) { |
| 669 | 0 | if (obj == this[i]) |
| 670 | 0 | return i; |
| 671 | } | |
| 672 | 0 | return -1; |
| 673 | }; | |
| 674 | ||
| 675 | /*! | |
| 676 | * Module exports. | |
| 677 | */ | |
| 678 | ||
| 679 | 1 | module.exports = exports = MongooseArray; |
| 680 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Access driver. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; |
| 7 | ||
| 8 | /*! | |
| 9 | * Module dependencies. | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var Binary = require(driver + '/binary'); |
| 13 | ||
| 14 | /** | |
| 15 | * Mongoose Buffer constructor. | |
| 16 | * | |
| 17 | * Values always have to be passed to the constructor to initialize. | |
| 18 | * | |
| 19 | * @param {Buffer} value | |
| 20 | * @param {String} encode | |
| 21 | * @param {Number} offset | |
| 22 | * @api private | |
| 23 | * @inherits Buffer | |
| 24 | * @see http://bit.ly/f6CnZU | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | function MongooseBuffer (value, encode, offset) { |
| 28 | 0 | var length = arguments.length; |
| 29 | 0 | var val; |
| 30 | ||
| 31 | 0 | if (0 === length || null === arguments[0] || undefined === arguments[0]) { |
| 32 | 0 | val = 0; |
| 33 | } else { | |
| 34 | 0 | val = value; |
| 35 | } | |
| 36 | ||
| 37 | 0 | var encoding; |
| 38 | 0 | var path; |
| 39 | 0 | var doc; |
| 40 | ||
| 41 | 0 | if (Array.isArray(encode)) { |
| 42 | // internal casting | |
| 43 | 0 | path = encode[0]; |
| 44 | 0 | doc = encode[1]; |
| 45 | } else { | |
| 46 | 0 | encoding = encode; |
| 47 | } | |
| 48 | ||
| 49 | 0 | var buf = new Buffer(val, encoding, offset); |
| 50 | 0 | buf.__proto__ = MongooseBuffer.prototype; |
| 51 | ||
| 52 | // make sure these internal props don't show up in Object.keys() | |
| 53 | 0 | Object.defineProperties(buf, { |
| 54 | validators: { value: [] } | |
| 55 | , _path: { value: path } | |
| 56 | , _parent: { value: doc } | |
| 57 | }); | |
| 58 | ||
| 59 | 0 | if (doc && "string" === typeof path) { |
| 60 | 0 | Object.defineProperty(buf, '_schema', { |
| 61 | value: doc.schema.path(path) | |
| 62 | }); | |
| 63 | } | |
| 64 | ||
| 65 | 0 | buf._subtype = 0; |
| 66 | 0 | return buf; |
| 67 | }; | |
| 68 | ||
| 69 | /*! | |
| 70 | * Inherit from Buffer. | |
| 71 | */ | |
| 72 | ||
| 73 | 1 | MongooseBuffer.prototype = new Buffer(0); |
| 74 | ||
| 75 | /** | |
| 76 | * Parent owner document | |
| 77 | * | |
| 78 | * @api private | |
| 79 | * @property _parent | |
| 80 | */ | |
| 81 | ||
| 82 | 1 | MongooseBuffer.prototype._parent; |
| 83 | ||
| 84 | /** | |
| 85 | * Default subtype for the Binary representing this Buffer | |
| 86 | * | |
| 87 | * @api private | |
| 88 | * @property _subtype | |
| 89 | */ | |
| 90 | ||
| 91 | 1 | MongooseBuffer.prototype._subtype; |
| 92 | ||
| 93 | /** | |
| 94 | * Marks this buffer as modified. | |
| 95 | * | |
| 96 | * @api private | |
| 97 | */ | |
| 98 | ||
| 99 | 1 | MongooseBuffer.prototype._markModified = function () { |
| 100 | 0 | var parent = this._parent; |
| 101 | ||
| 102 | 0 | if (parent) { |
| 103 | 0 | parent.markModified(this._path); |
| 104 | } | |
| 105 | 0 | return this; |
| 106 | }; | |
| 107 | ||
| 108 | /** | |
| 109 | * Writes the buffer. | |
| 110 | */ | |
| 111 | ||
| 112 | 1 | MongooseBuffer.prototype.write = function () { |
| 113 | 0 | var written = Buffer.prototype.write.apply(this, arguments); |
| 114 | ||
| 115 | 0 | if (written > 0) { |
| 116 | 0 | this._markModified(); |
| 117 | } | |
| 118 | ||
| 119 | 0 | return written; |
| 120 | }; | |
| 121 | ||
| 122 | /** | |
| 123 | * Copies the buffer. | |
| 124 | * | |
| 125 | * ####Note: | |
| 126 | * | |
| 127 | * `Buffer#copy` does not mark `target` as modified so you must copy from a `MongooseBuffer` for it to work as expected. This is a work around since `copy` modifies the target, not this. | |
| 128 | * | |
| 129 | * @return {MongooseBuffer} | |
| 130 | * @param {Buffer} target | |
| 131 | */ | |
| 132 | ||
| 133 | 1 | MongooseBuffer.prototype.copy = function (target) { |
| 134 | 0 | var ret = Buffer.prototype.copy.apply(this, arguments); |
| 135 | ||
| 136 | 0 | if (target instanceof MongooseBuffer) { |
| 137 | 0 | target._markModified(); |
| 138 | } | |
| 139 | ||
| 140 | 0 | return ret; |
| 141 | }; | |
| 142 | ||
| 143 | /*! | |
| 144 | * Compile other Buffer methods marking this buffer as modified. | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | ;( |
| 148 | // node < 0.5 | |
| 149 | 'writeUInt8 writeUInt16 writeUInt32 writeInt8 writeInt16 writeInt32 ' + | |
| 150 | 'writeFloat writeDouble fill ' + | |
| 151 | 'utf8Write binaryWrite asciiWrite set ' + | |
| 152 | ||
| 153 | // node >= 0.5 | |
| 154 | 'writeUInt16LE writeUInt16BE writeUInt32LE writeUInt32BE ' + | |
| 155 | 'writeInt16LE writeInt16BE writeInt32LE writeInt32BE ' + | |
| 156 | 'writeFloatLE writeFloatBE writeDoubleLE writeDoubleBE' | |
| 157 | ).split(' ').forEach(function (method) { | |
| 158 | 31 | if (!Buffer.prototype[method]) return; |
| 159 | 19 | MongooseBuffer.prototype[method] = new Function( |
| 160 | 'var ret = Buffer.prototype.'+method+'.apply(this, arguments);' + | |
| 161 | 'this._markModified();' + | |
| 162 | 'return ret;' | |
| 163 | ) | |
| 164 | }); | |
| 165 | ||
| 166 | /** | |
| 167 | * Converts this buffer to its Binary type representation. | |
| 168 | * | |
| 169 | * ####SubTypes: | |
| 170 | * | |
| 171 | * var bson = require('bson') | |
| 172 | * bson.BSON_BINARY_SUBTYPE_DEFAULT | |
| 173 | * bson.BSON_BINARY_SUBTYPE_FUNCTION | |
| 174 | * bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY | |
| 175 | * bson.BSON_BINARY_SUBTYPE_UUID | |
| 176 | * bson.BSON_BINARY_SUBTYPE_MD5 | |
| 177 | * bson.BSON_BINARY_SUBTYPE_USER_DEFINED | |
| 178 | * | |
| 179 | * doc.buffer.toObject(bson.BSON_BINARY_SUBTYPE_USER_DEFINED); | |
| 180 | * | |
| 181 | * @see http://bsonspec.org/#/specification | |
| 182 | * @param {Hex} [subtype] | |
| 183 | * @return {Binary} | |
| 184 | * @api public | |
| 185 | */ | |
| 186 | ||
| 187 | 1 | MongooseBuffer.prototype.toObject = function (options) { |
| 188 | 0 | var subtype = 'number' == typeof options |
| 189 | ? options | |
| 190 | : (this._subtype || 0); | |
| 191 | 0 | return new Binary(this, subtype); |
| 192 | }; | |
| 193 | ||
| 194 | /** | |
| 195 | * Determines if this buffer is equals to `other` buffer | |
| 196 | * | |
| 197 | * @param {Buffer} other | |
| 198 | * @return {Boolean} | |
| 199 | */ | |
| 200 | ||
| 201 | 1 | MongooseBuffer.prototype.equals = function (other) { |
| 202 | 0 | if (!Buffer.isBuffer(other)) { |
| 203 | 0 | return false; |
| 204 | } | |
| 205 | ||
| 206 | 0 | if (this.length !== other.length) { |
| 207 | 0 | return false; |
| 208 | } | |
| 209 | ||
| 210 | 0 | for (var i = 0; i < this.length; ++i) { |
| 211 | 0 | if (this[i] !== other[i]) return false; |
| 212 | } | |
| 213 | ||
| 214 | 0 | return true; |
| 215 | } | |
| 216 | ||
| 217 | /** | |
| 218 | * Sets the subtype option and marks the buffer modified. | |
| 219 | * | |
| 220 | * ####SubTypes: | |
| 221 | * | |
| 222 | * var bson = require('bson') | |
| 223 | * bson.BSON_BINARY_SUBTYPE_DEFAULT | |
| 224 | * bson.BSON_BINARY_SUBTYPE_FUNCTION | |
| 225 | * bson.BSON_BINARY_SUBTYPE_BYTE_ARRAY | |
| 226 | * bson.BSON_BINARY_SUBTYPE_UUID | |
| 227 | * bson.BSON_BINARY_SUBTYPE_MD5 | |
| 228 | * bson.BSON_BINARY_SUBTYPE_USER_DEFINED | |
| 229 | * | |
| 230 | * doc.buffer.subtype(bson.BSON_BINARY_SUBTYPE_UUID); | |
| 231 | * | |
| 232 | * @see http://bsonspec.org/#/specification | |
| 233 | * @param {Hex} subtype | |
| 234 | * @api public | |
| 235 | */ | |
| 236 | ||
| 237 | 1 | MongooseBuffer.prototype.subtype = function (subtype) { |
| 238 | 0 | if ('number' != typeof subtype) { |
| 239 | 0 | throw new TypeError('Invalid subtype. Expected a number'); |
| 240 | } | |
| 241 | ||
| 242 | 0 | if (this._subtype != subtype) { |
| 243 | 0 | this._markModified(); |
| 244 | } | |
| 245 | ||
| 246 | 0 | this._subtype = subtype; |
| 247 | } | |
| 248 | ||
| 249 | /*! | |
| 250 | * Module exports. | |
| 251 | */ | |
| 252 | ||
| 253 | 1 | MongooseBuffer.Binary = Binary; |
| 254 | ||
| 255 | 1 | module.exports = MongooseBuffer; |
| 256 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var MongooseArray = require('./array') |
| 7 | , driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native' | |
| 8 | , ObjectId = require(driver + '/objectid') | |
| 9 | , ObjectIdSchema = require('../schema/objectid') | |
| 10 | , utils = require('../utils') | |
| 11 | , util = require('util') | |
| 12 | , Document = require('../document') | |
| 13 | ||
| 14 | /** | |
| 15 | * DocumentArray constructor | |
| 16 | * | |
| 17 | * @param {Array} values | |
| 18 | * @param {String} path the path to this array | |
| 19 | * @param {Document} doc parent document | |
| 20 | * @api private | |
| 21 | * @return {MongooseDocumentArray} | |
| 22 | * @inherits MongooseArray | |
| 23 | * @see http://bit.ly/f6CnZU | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | function MongooseDocumentArray (values, path, doc) { |
| 27 | 0 | var arr = []; |
| 28 | ||
| 29 | // Values always have to be passed to the constructor to initialize, since | |
| 30 | // otherwise MongooseArray#push will mark the array as modified to the parent. | |
| 31 | 0 | arr.push.apply(arr, values); |
| 32 | 0 | arr.__proto__ = MongooseDocumentArray.prototype; |
| 33 | ||
| 34 | 0 | arr._atomics = {}; |
| 35 | 0 | arr.validators = []; |
| 36 | 0 | arr._path = path; |
| 37 | ||
| 38 | 0 | if (doc) { |
| 39 | 0 | arr._parent = doc; |
| 40 | 0 | arr._schema = doc.schema.path(path); |
| 41 | 0 | doc.on('save', arr.notify('save')); |
| 42 | 0 | doc.on('isNew', arr.notify('isNew')); |
| 43 | } | |
| 44 | ||
| 45 | 0 | return arr; |
| 46 | }; | |
| 47 | ||
| 48 | /*! | |
| 49 | * Inherits from MongooseArray | |
| 50 | */ | |
| 51 | ||
| 52 | 1 | MongooseDocumentArray.prototype.__proto__ = MongooseArray.prototype; |
| 53 | ||
| 54 | /** | |
| 55 | * Overrides MongooseArray#cast | |
| 56 | * | |
| 57 | * @api private | |
| 58 | */ | |
| 59 | ||
| 60 | 1 | MongooseDocumentArray.prototype._cast = function (value) { |
| 61 | 0 | if (value instanceof this._schema.casterConstructor) { |
| 62 | 0 | if (!(value.__parent && value.__parentArray)) { |
| 63 | // value may have been created using array.create() | |
| 64 | 0 | value.__parent = this._parent; |
| 65 | 0 | value.__parentArray = this; |
| 66 | } | |
| 67 | 0 | return value; |
| 68 | } | |
| 69 | ||
| 70 | // handle cast('string') or cast(ObjectId) etc. | |
| 71 | // only objects are permitted so we can safely assume that | |
| 72 | // non-objects are to be interpreted as _id | |
| 73 | 0 | if (Buffer.isBuffer(value) || |
| 74 | value instanceof ObjectId || !utils.isObject(value)) { | |
| 75 | 0 | value = { _id: value }; |
| 76 | } | |
| 77 | ||
| 78 | 0 | return new this._schema.casterConstructor(value, this); |
| 79 | }; | |
| 80 | ||
| 81 | /** | |
| 82 | * Searches array items for the first document with a matching _id. | |
| 83 | * | |
| 84 | * ####Example: | |
| 85 | * | |
| 86 | * var embeddedDoc = m.array.id(some_id); | |
| 87 | * | |
| 88 | * @return {EmbeddedDocument|null} the subdocuent or null if not found. | |
| 89 | * @param {ObjectId|String|Number|Buffer} id | |
| 90 | * @TODO cast to the _id based on schema for proper comparison | |
| 91 | * @api public | |
| 92 | */ | |
| 93 | ||
| 94 | 1 | MongooseDocumentArray.prototype.id = function (id) { |
| 95 | 0 | var casted |
| 96 | , sid | |
| 97 | , _id | |
| 98 | ||
| 99 | 0 | try { |
| 100 | 0 | var casted_ = ObjectIdSchema.prototype.cast.call({}, id); |
| 101 | 0 | if (casted_) casted = String(casted_); |
| 102 | } catch (e) { | |
| 103 | 0 | casted = null; |
| 104 | } | |
| 105 | ||
| 106 | 0 | for (var i = 0, l = this.length; i < l; i++) { |
| 107 | 0 | _id = this[i].get('_id'); |
| 108 | ||
| 109 | 0 | if (_id instanceof Document) { |
| 110 | 0 | sid || (sid = String(id)); |
| 111 | 0 | if (sid == _id._id) return this[i]; |
| 112 | 0 | } else if (!(_id instanceof ObjectId)) { |
| 113 | 0 | sid || (sid = String(id)); |
| 114 | 0 | if (sid == _id) return this[i]; |
| 115 | 0 | } else if (casted == _id) { |
| 116 | 0 | return this[i]; |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | 0 | return null; |
| 121 | }; | |
| 122 | ||
| 123 | /** | |
| 124 | * Returns a native js Array of plain js objects | |
| 125 | * | |
| 126 | * ####NOTE: | |
| 127 | * | |
| 128 | * _Each sub-document is converted to a plain object by calling its `#toObject` method._ | |
| 129 | * | |
| 130 | * @param {Object} [options] optional options to pass to each documents `toObject` method call during conversion | |
| 131 | * @return {Array} | |
| 132 | * @api public | |
| 133 | */ | |
| 134 | ||
| 135 | 1 | MongooseDocumentArray.prototype.toObject = function (options) { |
| 136 | 0 | return this.map(function (doc) { |
| 137 | 0 | return doc && doc.toObject(options) || null; |
| 138 | }); | |
| 139 | }; | |
| 140 | ||
| 141 | /** | |
| 142 | * Helper for console.log | |
| 143 | * | |
| 144 | * @api public | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | MongooseDocumentArray.prototype.inspect = function () { |
| 148 | 0 | return '[' + this.map(function (doc) { |
| 149 | 0 | if (doc) { |
| 150 | 0 | return doc.inspect |
| 151 | ? doc.inspect() | |
| 152 | : util.inspect(doc) | |
| 153 | } | |
| 154 | 0 | return 'null' |
| 155 | }).join('\n') + ']'; | |
| 156 | }; | |
| 157 | ||
| 158 | /** | |
| 159 | * Creates a subdocument casted to this schema. | |
| 160 | * | |
| 161 | * This is the same subdocument constructor used for casting. | |
| 162 | * | |
| 163 | * @param {Object} obj the value to cast to this arrays SubDocument schema | |
| 164 | * @api public | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | MongooseDocumentArray.prototype.create = function (obj) { |
| 168 | 0 | return new this._schema.casterConstructor(obj); |
| 169 | } | |
| 170 | ||
| 171 | /** | |
| 172 | * Creates a fn that notifies all child docs of `event`. | |
| 173 | * | |
| 174 | * @param {String} event | |
| 175 | * @return {Function} | |
| 176 | * @api private | |
| 177 | */ | |
| 178 | ||
| 179 | 1 | MongooseDocumentArray.prototype.notify = function notify (event) { |
| 180 | 0 | var self = this; |
| 181 | 0 | return function notify (val) { |
| 182 | 0 | var i = self.length; |
| 183 | 0 | while (i--) { |
| 184 | 0 | if (!self[i]) continue; |
| 185 | 0 | self[i].emit(event, val); |
| 186 | } | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | /*! | |
| 191 | * Module exports. | |
| 192 | */ | |
| 193 | ||
| 194 | 1 | module.exports = MongooseDocumentArray; |
| 195 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var Document = require('../document') |
| 6 | , inspect = require('util').inspect; | |
| 7 | ||
| 8 | /** | |
| 9 | * EmbeddedDocument constructor. | |
| 10 | * | |
| 11 | * @param {Object} obj js object returned from the db | |
| 12 | * @param {MongooseDocumentArray} parentArr the parent array of this document | |
| 13 | * @param {Boolean} skipId | |
| 14 | * @inherits Document | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | function EmbeddedDocument (obj, parentArr, skipId, fields) { |
| 19 | 0 | if (parentArr) { |
| 20 | 0 | this.__parentArray = parentArr; |
| 21 | 0 | this.__parent = parentArr._parent; |
| 22 | } else { | |
| 23 | 0 | this.__parentArray = undefined; |
| 24 | 0 | this.__parent = undefined; |
| 25 | } | |
| 26 | ||
| 27 | 0 | Document.call(this, obj, fields, skipId); |
| 28 | ||
| 29 | 0 | var self = this; |
| 30 | 0 | this.on('isNew', function (val) { |
| 31 | 0 | self.isNew = val; |
| 32 | }); | |
| 33 | }; | |
| 34 | ||
| 35 | /*! | |
| 36 | * Inherit from Document | |
| 37 | */ | |
| 38 | ||
| 39 | 1 | EmbeddedDocument.prototype.__proto__ = Document.prototype; |
| 40 | ||
| 41 | /** | |
| 42 | * Marks the embedded doc modified. | |
| 43 | * | |
| 44 | * ####Example: | |
| 45 | * | |
| 46 | * var doc = blogpost.comments.id(hexstring); | |
| 47 | * doc.mixed.type = 'changed'; | |
| 48 | * doc.markModified('mixed.type'); | |
| 49 | * | |
| 50 | * @param {String} path the path which changed | |
| 51 | * @api public | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | EmbeddedDocument.prototype.markModified = function (path) { |
| 55 | 0 | if (!this.__parentArray) return; |
| 56 | ||
| 57 | 0 | this.$__.activePaths.modify(path); |
| 58 | ||
| 59 | 0 | if (this.isNew) { |
| 60 | // Mark the WHOLE parent array as modified | |
| 61 | // if this is a new document (i.e., we are initializing | |
| 62 | // a document), | |
| 63 | 0 | this.__parentArray._markModified(); |
| 64 | } else | |
| 65 | 0 | this.__parentArray._markModified(this, path); |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Used as a stub for [hooks.js](https://github.com/bnoguchi/hooks-js/tree/31ec571cef0332e21121ee7157e0cf9728572cc3) | |
| 70 | * | |
| 71 | * ####NOTE: | |
| 72 | * | |
| 73 | * _This is a no-op. Does not actually save the doc to the db._ | |
| 74 | * | |
| 75 | * @param {Function} [fn] | |
| 76 | * @return {EmbeddedDocument} this | |
| 77 | * @api private | |
| 78 | */ | |
| 79 | ||
| 80 | 1 | EmbeddedDocument.prototype.save = function(fn) { |
| 81 | 0 | if (fn) |
| 82 | 0 | fn(null); |
| 83 | 0 | return this; |
| 84 | }; | |
| 85 | ||
| 86 | /** | |
| 87 | * Removes the subdocument from its parent array. | |
| 88 | * | |
| 89 | * @param {Function} [fn] | |
| 90 | * @api public | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | EmbeddedDocument.prototype.remove = function (fn) { |
| 94 | 0 | if (!this.__parentArray) return this; |
| 95 | ||
| 96 | 0 | var _id; |
| 97 | 0 | if (!this.willRemove) { |
| 98 | 0 | _id = this._doc._id; |
| 99 | 0 | if (!_id) { |
| 100 | 0 | throw new Error('For your own good, Mongoose does not know ' + |
| 101 | 'how to remove an EmbeddedDocument that has no _id'); | |
| 102 | } | |
| 103 | 0 | this.__parentArray.pull({ _id: _id }); |
| 104 | 0 | this.willRemove = true; |
| 105 | 0 | registerRemoveListener(this); |
| 106 | } | |
| 107 | ||
| 108 | 0 | if (fn) |
| 109 | 0 | fn(null); |
| 110 | ||
| 111 | 0 | return this; |
| 112 | }; | |
| 113 | ||
| 114 | /*! | |
| 115 | * Registers remove event listeners for triggering | |
| 116 | * on subdocuments. | |
| 117 | * | |
| 118 | * @param {EmbeddedDocument} sub | |
| 119 | * @api private | |
| 120 | */ | |
| 121 | ||
| 122 | 1 | function registerRemoveListener (sub) { |
| 123 | 0 | var owner = sub.ownerDocument(); |
| 124 | ||
| 125 | 0 | owner.on('save', emitRemove); |
| 126 | 0 | owner.on('remove', emitRemove); |
| 127 | ||
| 128 | 0 | function emitRemove () { |
| 129 | 0 | owner.removeListener('save', emitRemove); |
| 130 | 0 | owner.removeListener('remove', emitRemove); |
| 131 | 0 | sub.emit('remove', sub); |
| 132 | 0 | owner = sub = emitRemove = null; |
| 133 | }; | |
| 134 | }; | |
| 135 | ||
| 136 | /** | |
| 137 | * Override #update method of parent documents. | |
| 138 | * @api private | |
| 139 | */ | |
| 140 | ||
| 141 | 1 | EmbeddedDocument.prototype.update = function () { |
| 142 | 0 | throw new Error('The #update method is not available on EmbeddedDocuments'); |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * Helper for console.log | |
| 147 | * | |
| 148 | * @api public | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | EmbeddedDocument.prototype.inspect = function () { |
| 152 | 0 | return inspect(this.toObject()); |
| 153 | }; | |
| 154 | ||
| 155 | /** | |
| 156 | * Marks a path as invalid, causing validation to fail. | |
| 157 | * | |
| 158 | * @param {String} path the field to invalidate | |
| 159 | * @param {String|Error} err error which states the reason `path` was invalid | |
| 160 | * @return {Boolean} | |
| 161 | * @api public | |
| 162 | */ | |
| 163 | ||
| 164 | 1 | EmbeddedDocument.prototype.invalidate = function (path, err, val, first) { |
| 165 | 0 | if (!this.__parent) { |
| 166 | 0 | var msg = 'Unable to invalidate a subdocument that has not been added to an array.' |
| 167 | 0 | throw new Error(msg); |
| 168 | } | |
| 169 | ||
| 170 | 0 | var index = this.__parentArray.indexOf(this); |
| 171 | 0 | var parentPath = this.__parentArray._path; |
| 172 | 0 | var fullPath = [parentPath, index, path].join('.'); |
| 173 | ||
| 174 | // sniffing arguments: | |
| 175 | // need to check if user passed a value to keep | |
| 176 | // our error message clean. | |
| 177 | 0 | if (2 < arguments.length) { |
| 178 | 0 | this.__parent.invalidate(fullPath, err, val); |
| 179 | } else { | |
| 180 | 0 | this.__parent.invalidate(fullPath, err); |
| 181 | } | |
| 182 | ||
| 183 | 0 | if (first) |
| 184 | 0 | this.$__.validationError = this.ownerDocument().$__.validationError; |
| 185 | 0 | return true; |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * Returns the top level document of this sub-document. | |
| 190 | * | |
| 191 | * @return {Document} | |
| 192 | */ | |
| 193 | ||
| 194 | 1 | EmbeddedDocument.prototype.ownerDocument = function () { |
| 195 | 0 | if (this.$__.ownerDocument) { |
| 196 | 0 | return this.$__.ownerDocument; |
| 197 | } | |
| 198 | ||
| 199 | 0 | var parent = this.__parent; |
| 200 | 0 | if (!parent) return this; |
| 201 | ||
| 202 | 0 | while (parent.__parent) { |
| 203 | 0 | parent = parent.__parent; |
| 204 | } | |
| 205 | ||
| 206 | 0 | return this.$__.ownerDocument = parent; |
| 207 | } | |
| 208 | ||
| 209 | /** | |
| 210 | * Returns the full path to this document. If optional `path` is passed, it is appended to the full path. | |
| 211 | * | |
| 212 | * @param {String} [path] | |
| 213 | * @return {String} | |
| 214 | * @api private | |
| 215 | * @method $__fullPath | |
| 216 | * @memberOf EmbeddedDocument | |
| 217 | */ | |
| 218 | ||
| 219 | 1 | EmbeddedDocument.prototype.$__fullPath = function (path) { |
| 220 | 0 | if (!this.$__.fullPath) { |
| 221 | 0 | var parent = this; |
| 222 | 0 | if (!parent.__parent) return path; |
| 223 | ||
| 224 | 0 | var paths = []; |
| 225 | 0 | while (parent.__parent) { |
| 226 | 0 | paths.unshift(parent.__parentArray._path); |
| 227 | 0 | parent = parent.__parent; |
| 228 | } | |
| 229 | ||
| 230 | 0 | this.$__.fullPath = paths.join('.'); |
| 231 | ||
| 232 | 0 | if (!this.$__.ownerDocument) { |
| 233 | // optimization | |
| 234 | 0 | this.$__.ownerDocument = parent; |
| 235 | } | |
| 236 | } | |
| 237 | ||
| 238 | 0 | return path |
| 239 | ? this.$__.fullPath + '.' + path | |
| 240 | : this.$__.fullPath; | |
| 241 | } | |
| 242 | ||
| 243 | /** | |
| 244 | * Returns this sub-documents parent document. | |
| 245 | * | |
| 246 | * @api public | |
| 247 | */ | |
| 248 | ||
| 249 | 1 | EmbeddedDocument.prototype.parent = function () { |
| 250 | 0 | return this.__parent; |
| 251 | } | |
| 252 | ||
| 253 | /** | |
| 254 | * Returns this sub-documents parent array. | |
| 255 | * | |
| 256 | * @api public | |
| 257 | */ | |
| 258 | ||
| 259 | 1 | EmbeddedDocument.prototype.parentArray = function () { |
| 260 | 0 | return this.__parentArray; |
| 261 | } | |
| 262 | ||
| 263 | /*! | |
| 264 | * Module exports. | |
| 265 | */ | |
| 266 | ||
| 267 | 1 | module.exports = EmbeddedDocument; |
| 268 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Module exports. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | exports.Array = require('./array'); |
| 7 | 1 | exports.Buffer = require('./buffer'); |
| 8 | ||
| 9 | 1 | exports.Document = // @deprecate |
| 10 | exports.Embedded = require('./embedded'); | |
| 11 | ||
| 12 | 1 | exports.DocumentArray = require('./documentarray'); |
| 13 | 1 | exports.ObjectId = require('./objectid'); |
| 14 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /*! | |
| 3 | * Access driver. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native'; |
| 7 | ||
| 8 | /** | |
| 9 | * ObjectId type constructor | |
| 10 | * | |
| 11 | * ####Example | |
| 12 | * | |
| 13 | * var id = new mongoose.Types.ObjectId; | |
| 14 | * | |
| 15 | * @constructor ObjectId | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | var ObjectId = require(driver + '/objectid'); |
| 19 | 1 | module.exports = ObjectId; |
| 20 | ||
| 21 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var ReadPref = require('mongodb').ReadPreference |
| 6 | , ObjectId = require('./types/objectid') | |
| 7 | , cloneRegExp = require('regexp-clone') | |
| 8 | , sliced = require('sliced') | |
| 9 | , mpath = require('mpath') | |
| 10 | , ms = require('ms') | |
| 11 | , MongooseBuffer | |
| 12 | , MongooseArray | |
| 13 | , Document | |
| 14 | ||
| 15 | /*! | |
| 16 | * Produces a collection name from model `name`. | |
| 17 | * | |
| 18 | * @param {String} name a model name | |
| 19 | * @return {String} a collection name | |
| 20 | * @api private | |
| 21 | */ | |
| 22 | ||
| 23 | 1 | exports.toCollectionName = function (name, options) { |
| 24 | 0 | options = options || {}; |
| 25 | 0 | if ('system.profile' === name) return name; |
| 26 | 0 | if ('system.indexes' === name) return name; |
| 27 | 0 | if (options.pluralization === false) return name; |
| 28 | 0 | return pluralize(name.toLowerCase()); |
| 29 | }; | |
| 30 | ||
| 31 | /** | |
| 32 | * Pluralization rules. | |
| 33 | * | |
| 34 | * These rules are applied while processing the argument to `toCollectionName`. | |
| 35 | * | |
| 36 | * @deprecated remove in 4.x gh-1350 | |
| 37 | */ | |
| 38 | ||
| 39 | 1 | exports.pluralization = [ |
| 40 | [/(m)an$/gi, '$1en'], | |
| 41 | [/(pe)rson$/gi, '$1ople'], | |
| 42 | [/(child)$/gi, '$1ren'], | |
| 43 | [/^(ox)$/gi, '$1en'], | |
| 44 | [/(ax|test)is$/gi, '$1es'], | |
| 45 | [/(octop|vir)us$/gi, '$1i'], | |
| 46 | [/(alias|status)$/gi, '$1es'], | |
| 47 | [/(bu)s$/gi, '$1ses'], | |
| 48 | [/(buffal|tomat|potat)o$/gi, '$1oes'], | |
| 49 | [/([ti])um$/gi, '$1a'], | |
| 50 | [/sis$/gi, 'ses'], | |
| 51 | [/(?:([^f])fe|([lr])f)$/gi, '$1$2ves'], | |
| 52 | [/(hive)$/gi, '$1s'], | |
| 53 | [/([^aeiouy]|qu)y$/gi, '$1ies'], | |
| 54 | [/(x|ch|ss|sh)$/gi, '$1es'], | |
| 55 | [/(matr|vert|ind)ix|ex$/gi, '$1ices'], | |
| 56 | [/([m|l])ouse$/gi, '$1ice'], | |
| 57 | [/(quiz)$/gi, '$1zes'], | |
| 58 | [/s$/gi, 's'], | |
| 59 | [/([^a-z])$/, '$1'], | |
| 60 | [/$/gi, 's'] | |
| 61 | ]; | |
| 62 | 1 | var rules = exports.pluralization; |
| 63 | ||
| 64 | /** | |
| 65 | * Uncountable words. | |
| 66 | * | |
| 67 | * These words are applied while processing the argument to `toCollectionName`. | |
| 68 | * @api public | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | exports.uncountables = [ |
| 72 | 'advice', | |
| 73 | 'energy', | |
| 74 | 'excretion', | |
| 75 | 'digestion', | |
| 76 | 'cooperation', | |
| 77 | 'health', | |
| 78 | 'justice', | |
| 79 | 'labour', | |
| 80 | 'machinery', | |
| 81 | 'equipment', | |
| 82 | 'information', | |
| 83 | 'pollution', | |
| 84 | 'sewage', | |
| 85 | 'paper', | |
| 86 | 'money', | |
| 87 | 'species', | |
| 88 | 'series', | |
| 89 | 'rain', | |
| 90 | 'rice', | |
| 91 | 'fish', | |
| 92 | 'sheep', | |
| 93 | 'moose', | |
| 94 | 'deer', | |
| 95 | 'news', | |
| 96 | 'expertise', | |
| 97 | 'status', | |
| 98 | 'media' | |
| 99 | ]; | |
| 100 | 1 | var uncountables = exports.uncountables; |
| 101 | ||
| 102 | /*! | |
| 103 | * Pluralize function. | |
| 104 | * | |
| 105 | * @author TJ Holowaychuk (extracted from _ext.js_) | |
| 106 | * @param {String} string to pluralize | |
| 107 | * @api private | |
| 108 | */ | |
| 109 | ||
| 110 | 1 | function pluralize (str) { |
| 111 | 0 | var rule, found; |
| 112 | 0 | if (!~uncountables.indexOf(str.toLowerCase())){ |
| 113 | 0 | found = rules.filter(function(rule){ |
| 114 | 0 | return str.match(rule[0]); |
| 115 | }); | |
| 116 | 0 | if (found[0]) return str.replace(found[0][0], found[0][1]); |
| 117 | } | |
| 118 | 0 | return str; |
| 119 | }; | |
| 120 | ||
| 121 | /*! | |
| 122 | * Determines if `a` and `b` are deep equal. | |
| 123 | * | |
| 124 | * Modified from node/lib/assert.js | |
| 125 | * | |
| 126 | * @param {any} a a value to compare to `b` | |
| 127 | * @param {any} b a value to compare to `a` | |
| 128 | * @return {Boolean} | |
| 129 | * @api private | |
| 130 | */ | |
| 131 | ||
| 132 | 1 | exports.deepEqual = function deepEqual (a, b) { |
| 133 | 0 | if (a === b) return true; |
| 134 | ||
| 135 | 0 | if (a instanceof Date && b instanceof Date) |
| 136 | 0 | return a.getTime() === b.getTime(); |
| 137 | ||
| 138 | 0 | if (a instanceof ObjectId && b instanceof ObjectId) { |
| 139 | 0 | return a.toString() === b.toString(); |
| 140 | } | |
| 141 | ||
| 142 | 0 | if (a instanceof RegExp && b instanceof RegExp) { |
| 143 | 0 | return a.source == b.source && |
| 144 | a.ignoreCase == b.ignoreCase && | |
| 145 | a.multiline == b.multiline && | |
| 146 | a.global == b.global; | |
| 147 | } | |
| 148 | ||
| 149 | 0 | if (typeof a !== 'object' && typeof b !== 'object') |
| 150 | 0 | return a == b; |
| 151 | ||
| 152 | 0 | if (a === null || b === null || a === undefined || b === undefined) |
| 153 | 0 | return false |
| 154 | ||
| 155 | 0 | if (a.prototype !== b.prototype) return false; |
| 156 | ||
| 157 | // Handle MongooseNumbers | |
| 158 | 0 | if (a instanceof Number && b instanceof Number) { |
| 159 | 0 | return a.valueOf() === b.valueOf(); |
| 160 | } | |
| 161 | ||
| 162 | 0 | if (Buffer.isBuffer(a)) { |
| 163 | 0 | return exports.buffer.areEqual(a, b); |
| 164 | } | |
| 165 | ||
| 166 | 0 | if (isMongooseObject(a)) a = a.toObject(); |
| 167 | 0 | if (isMongooseObject(b)) b = b.toObject(); |
| 168 | ||
| 169 | 0 | try { |
| 170 | 0 | var ka = Object.keys(a), |
| 171 | kb = Object.keys(b), | |
| 172 | key, i; | |
| 173 | } catch (e) {//happens when one is a string literal and the other isn't | |
| 174 | 0 | return false; |
| 175 | } | |
| 176 | ||
| 177 | // having the same number of owned properties (keys incorporates | |
| 178 | // hasOwnProperty) | |
| 179 | 0 | if (ka.length != kb.length) |
| 180 | 0 | return false; |
| 181 | ||
| 182 | //the same set of keys (although not necessarily the same order), | |
| 183 | 0 | ka.sort(); |
| 184 | 0 | kb.sort(); |
| 185 | ||
| 186 | //~~~cheap key test | |
| 187 | 0 | for (i = ka.length - 1; i >= 0; i--) { |
| 188 | 0 | if (ka[i] != kb[i]) |
| 189 | 0 | return false; |
| 190 | } | |
| 191 | ||
| 192 | //equivalent values for every corresponding key, and | |
| 193 | //~~~possibly expensive deep test | |
| 194 | 0 | for (i = ka.length - 1; i >= 0; i--) { |
| 195 | 0 | key = ka[i]; |
| 196 | 0 | if (!deepEqual(a[key], b[key])) return false; |
| 197 | } | |
| 198 | ||
| 199 | 0 | return true; |
| 200 | }; | |
| 201 | ||
| 202 | /*! | |
| 203 | * Object clone with Mongoose natives support. | |
| 204 | * | |
| 205 | * If options.minimize is true, creates a minimal data object. Empty objects and undefined values will not be cloned. This makes the data payload sent to MongoDB as small as possible. | |
| 206 | * | |
| 207 | * Functions are never cloned. | |
| 208 | * | |
| 209 | * @param {Object} obj the object to clone | |
| 210 | * @param {Object} options | |
| 211 | * @return {Object} the cloned object | |
| 212 | * @api private | |
| 213 | */ | |
| 214 | ||
| 215 | 1 | exports.clone = function clone (obj, options) { |
| 216 | 17 | if (obj === undefined || obj === null) |
| 217 | 0 | return obj; |
| 218 | ||
| 219 | 17 | if (Array.isArray(obj)) |
| 220 | 0 | return cloneArray(obj, options); |
| 221 | ||
| 222 | 17 | if (isMongooseObject(obj)) { |
| 223 | 0 | if (options && options.json && 'function' === typeof obj.toJSON) { |
| 224 | 0 | return obj.toJSON(options); |
| 225 | } else { | |
| 226 | 0 | return obj.toObject(options); |
| 227 | } | |
| 228 | } | |
| 229 | ||
| 230 | 17 | if (obj.constructor) { |
| 231 | 17 | switch (obj.constructor.name) { |
| 232 | case 'Object': | |
| 233 | 0 | return cloneObject(obj, options); |
| 234 | case 'Date': | |
| 235 | 0 | return new obj.constructor(+obj); |
| 236 | case 'RegExp': | |
| 237 | 0 | return cloneRegExp(obj); |
| 238 | default: | |
| 239 | // ignore | |
| 240 | 17 | break; |
| 241 | } | |
| 242 | } | |
| 243 | ||
| 244 | 17 | if (obj instanceof ObjectId) |
| 245 | 0 | return new ObjectId(obj.id); |
| 246 | ||
| 247 | 17 | if (!obj.constructor && exports.isObject(obj)) { |
| 248 | // object created with Object.create(null) | |
| 249 | 0 | return cloneObject(obj, options); |
| 250 | } | |
| 251 | ||
| 252 | 17 | if (obj.valueOf) |
| 253 | 17 | return obj.valueOf(); |
| 254 | }; | |
| 255 | 1 | var clone = exports.clone; |
| 256 | ||
| 257 | /*! | |
| 258 | * ignore | |
| 259 | */ | |
| 260 | ||
| 261 | 1 | function cloneObject (obj, options) { |
| 262 | 0 | var retainKeyOrder = options && options.retainKeyOrder |
| 263 | , minimize = options && options.minimize | |
| 264 | , ret = {} | |
| 265 | , hasKeys | |
| 266 | , keys | |
| 267 | , val | |
| 268 | , k | |
| 269 | , i | |
| 270 | ||
| 271 | 0 | if (retainKeyOrder) { |
| 272 | 0 | for (k in obj) { |
| 273 | 0 | val = clone(obj[k], options); |
| 274 | ||
| 275 | 0 | if (!minimize || ('undefined' !== typeof val)) { |
| 276 | 0 | hasKeys || (hasKeys = true); |
| 277 | 0 | ret[k] = val; |
| 278 | } | |
| 279 | } | |
| 280 | } else { | |
| 281 | // faster | |
| 282 | ||
| 283 | 0 | keys = Object.keys(obj); |
| 284 | 0 | i = keys.length; |
| 285 | ||
| 286 | 0 | while (i--) { |
| 287 | 0 | k = keys[i]; |
| 288 | 0 | val = clone(obj[k], options); |
| 289 | ||
| 290 | 0 | if (!minimize || ('undefined' !== typeof val)) { |
| 291 | 0 | if (!hasKeys) hasKeys = true; |
| 292 | 0 | ret[k] = val; |
| 293 | } | |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 297 | 0 | return minimize |
| 298 | ? hasKeys && ret | |
| 299 | : ret; | |
| 300 | }; | |
| 301 | ||
| 302 | 1 | function cloneArray (arr, options) { |
| 303 | 0 | var ret = []; |
| 304 | 0 | for (var i = 0, l = arr.length; i < l; i++) |
| 305 | 0 | ret.push(clone(arr[i], options)); |
| 306 | 0 | return ret; |
| 307 | }; | |
| 308 | ||
| 309 | /*! | |
| 310 | * Shallow copies defaults into options. | |
| 311 | * | |
| 312 | * @param {Object} defaults | |
| 313 | * @param {Object} options | |
| 314 | * @return {Object} the merged object | |
| 315 | * @api private | |
| 316 | */ | |
| 317 | ||
| 318 | 1 | exports.options = function (defaults, options) { |
| 319 | 1 | var keys = Object.keys(defaults) |
| 320 | , i = keys.length | |
| 321 | , k ; | |
| 322 | ||
| 323 | 1 | options = options || {}; |
| 324 | ||
| 325 | 1 | while (i--) { |
| 326 | 13 | k = keys[i]; |
| 327 | 13 | if (!(k in options)) { |
| 328 | 11 | options[k] = defaults[k]; |
| 329 | } | |
| 330 | } | |
| 331 | ||
| 332 | 1 | return options; |
| 333 | }; | |
| 334 | ||
| 335 | /*! | |
| 336 | * Generates a random string | |
| 337 | * | |
| 338 | * @api private | |
| 339 | */ | |
| 340 | ||
| 341 | 1 | exports.random = function () { |
| 342 | 0 | return Math.random().toString().substr(3); |
| 343 | }; | |
| 344 | ||
| 345 | /*! | |
| 346 | * Merges `from` into `to` without overwriting existing properties. | |
| 347 | * | |
| 348 | * @param {Object} to | |
| 349 | * @param {Object} from | |
| 350 | * @api private | |
| 351 | */ | |
| 352 | ||
| 353 | 1 | exports.merge = function merge (to, from) { |
| 354 | 0 | var keys = Object.keys(from) |
| 355 | , i = keys.length | |
| 356 | , key; | |
| 357 | ||
| 358 | 0 | while (i--) { |
| 359 | 0 | key = keys[i]; |
| 360 | 0 | if ('undefined' === typeof to[key]) { |
| 361 | 0 | to[key] = from[key]; |
| 362 | 0 | } else if (exports.isObject(from[key])) { |
| 363 | 0 | merge(to[key], from[key]); |
| 364 | } | |
| 365 | } | |
| 366 | }; | |
| 367 | ||
| 368 | /*! | |
| 369 | * toString helper | |
| 370 | */ | |
| 371 | ||
| 372 | 1 | var toString = Object.prototype.toString; |
| 373 | ||
| 374 | /*! | |
| 375 | * Determines if `arg` is an object. | |
| 376 | * | |
| 377 | * @param {Object|Array|String|Function|RegExp|any} arg | |
| 378 | * @api private | |
| 379 | * @return {Boolean} | |
| 380 | */ | |
| 381 | ||
| 382 | 1 | exports.isObject = function (arg) { |
| 383 | 17 | return '[object Object]' == toString.call(arg); |
| 384 | } | |
| 385 | ||
| 386 | /*! | |
| 387 | * A faster Array.prototype.slice.call(arguments) alternative | |
| 388 | * @api private | |
| 389 | */ | |
| 390 | ||
| 391 | 1 | exports.args = sliced; |
| 392 | ||
| 393 | /*! | |
| 394 | * process.nextTick helper. | |
| 395 | * | |
| 396 | * Wraps `callback` in a try/catch + nextTick. | |
| 397 | * | |
| 398 | * node-mongodb-native has a habit of state corruption when an error is immediately thrown from within a collection callback. | |
| 399 | * | |
| 400 | * @param {Function} callback | |
| 401 | * @api private | |
| 402 | */ | |
| 403 | ||
| 404 | 1 | exports.tick = function tick (callback) { |
| 405 | 0 | if ('function' !== typeof callback) return; |
| 406 | 0 | return function () { |
| 407 | 0 | try { |
| 408 | 0 | callback.apply(this, arguments); |
| 409 | } catch (err) { | |
| 410 | // only nextTick on err to get out of | |
| 411 | // the event loop and avoid state corruption. | |
| 412 | 0 | process.nextTick(function () { |
| 413 | 0 | throw err; |
| 414 | }); | |
| 415 | } | |
| 416 | } | |
| 417 | } | |
| 418 | ||
| 419 | /*! | |
| 420 | * Returns if `v` is a mongoose object that has a `toObject()` method we can use. | |
| 421 | * | |
| 422 | * This is for compatibility with libs like Date.js which do foolish things to Natives. | |
| 423 | * | |
| 424 | * @param {any} v | |
| 425 | * @api private | |
| 426 | */ | |
| 427 | ||
| 428 | 1 | exports.isMongooseObject = function (v) { |
| 429 | 17 | Document || (Document = require('./document')); |
| 430 | 17 | MongooseArray || (MongooseArray = require('./types').Array); |
| 431 | 17 | MongooseBuffer || (MongooseBuffer = require('./types').Buffer); |
| 432 | ||
| 433 | 17 | return v instanceof Document || |
| 434 | v instanceof MongooseArray || | |
| 435 | v instanceof MongooseBuffer | |
| 436 | } | |
| 437 | 1 | var isMongooseObject = exports.isMongooseObject; |
| 438 | ||
| 439 | /*! | |
| 440 | * Converts `expires` options of index objects to `expiresAfterSeconds` options for MongoDB. | |
| 441 | * | |
| 442 | * @param {Object} object | |
| 443 | * @api private | |
| 444 | */ | |
| 445 | ||
| 446 | 1 | exports.expires = function expires (object) { |
| 447 | 0 | if (!(object && 'Object' == object.constructor.name)) return; |
| 448 | 0 | if (!('expires' in object)) return; |
| 449 | ||
| 450 | 0 | var when; |
| 451 | 0 | if ('string' != typeof object.expires) { |
| 452 | 0 | when = object.expires; |
| 453 | } else { | |
| 454 | 0 | when = Math.round(ms(object.expires) / 1000); |
| 455 | } | |
| 456 | 0 | object.expireAfterSeconds = when; |
| 457 | 0 | delete object.expires; |
| 458 | } | |
| 459 | ||
| 460 | /*! | |
| 461 | * Converts arguments to ReadPrefs the driver | |
| 462 | * can understand. | |
| 463 | * | |
| 464 | * @TODO move this into the driver layer | |
| 465 | * @param {String|Array} pref | |
| 466 | * @param {Array} [tags] | |
| 467 | */ | |
| 468 | ||
| 469 | 1 | exports.readPref = function readPref (pref, tags) { |
| 470 | 0 | if (Array.isArray(pref)) { |
| 471 | 0 | tags = pref[1]; |
| 472 | 0 | pref = pref[0]; |
| 473 | } | |
| 474 | ||
| 475 | 0 | switch (pref) { |
| 476 | case 'p': | |
| 477 | 0 | pref = 'primary'; |
| 478 | 0 | break; |
| 479 | case 'pp': | |
| 480 | 0 | pref = 'primaryPreferred'; |
| 481 | 0 | break; |
| 482 | case 's': | |
| 483 | 0 | pref = 'secondary'; |
| 484 | 0 | break; |
| 485 | case 'sp': | |
| 486 | 0 | pref = 'secondaryPreferred'; |
| 487 | 0 | break; |
| 488 | case 'n': | |
| 489 | 0 | pref = 'nearest'; |
| 490 | 0 | break; |
| 491 | } | |
| 492 | ||
| 493 | 0 | return new ReadPref(pref, tags); |
| 494 | } | |
| 495 | ||
| 496 | /*! | |
| 497 | * Populate options constructor | |
| 498 | */ | |
| 499 | ||
| 500 | 1 | function PopulateOptions (path, select, match, options, model) { |
| 501 | 0 | this.path = path; |
| 502 | 0 | this.match = match; |
| 503 | 0 | this.select = select; |
| 504 | 0 | this.options = options; |
| 505 | 0 | this.model = model; |
| 506 | 0 | this._docs = {}; |
| 507 | } | |
| 508 | ||
| 509 | // make it compatible with utils.clone | |
| 510 | 1 | PopulateOptions.prototype.constructor = Object; |
| 511 | ||
| 512 | // expose | |
| 513 | 1 | exports.PopulateOptions = PopulateOptions; |
| 514 | ||
| 515 | /*! | |
| 516 | * populate helper | |
| 517 | */ | |
| 518 | ||
| 519 | 1 | exports.populate = function populate (path, select, model, match, options) { |
| 520 | // The order of select/conditions args is opposite Model.find but | |
| 521 | // necessary to keep backward compatibility (select could be | |
| 522 | // an array, string, or object literal). | |
| 523 | ||
| 524 | // might have passed an object specifying all arguments | |
| 525 | 0 | if (1 === arguments.length) { |
| 526 | 0 | if (path instanceof PopulateOptions) { |
| 527 | 0 | return [path]; |
| 528 | } | |
| 529 | ||
| 530 | 0 | if (Array.isArray(path)) { |
| 531 | 0 | return path.map(function(o){ |
| 532 | 0 | return exports.populate(o)[0]; |
| 533 | }); | |
| 534 | } | |
| 535 | ||
| 536 | 0 | if (exports.isObject(path)) { |
| 537 | 0 | match = path.match; |
| 538 | 0 | options = path.options; |
| 539 | 0 | select = path.select; |
| 540 | 0 | model = path.model; |
| 541 | 0 | path = path.path; |
| 542 | } | |
| 543 | 0 | } else if ('string' !== typeof model) { |
| 544 | 0 | options = match; |
| 545 | 0 | match = model; |
| 546 | 0 | model = undefined; |
| 547 | } | |
| 548 | ||
| 549 | 0 | if ('string' != typeof path) { |
| 550 | 0 | throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`'); |
| 551 | } | |
| 552 | ||
| 553 | 0 | var ret = []; |
| 554 | 0 | var paths = path.split(' '); |
| 555 | 0 | for (var i = 0; i < paths.length; ++i) { |
| 556 | 0 | ret.push(new PopulateOptions(paths[i], select, match, options, model)); |
| 557 | } | |
| 558 | ||
| 559 | 0 | return ret; |
| 560 | } | |
| 561 | ||
| 562 | /*! | |
| 563 | * Return the value of `obj` at the given `path`. | |
| 564 | * | |
| 565 | * @param {String} path | |
| 566 | * @param {Object} obj | |
| 567 | */ | |
| 568 | ||
| 569 | 1 | exports.getValue = function (path, obj, map) { |
| 570 | 0 | return mpath.get(path, obj, '_doc', map); |
| 571 | } | |
| 572 | ||
| 573 | /*! | |
| 574 | * Sets the value of `obj` at the given `path`. | |
| 575 | * | |
| 576 | * @param {String} path | |
| 577 | * @param {Anything} val | |
| 578 | * @param {Object} obj | |
| 579 | */ | |
| 580 | ||
| 581 | 1 | exports.setValue = function (path, val, obj, map) { |
| 582 | 0 | mpath.set(path, val, obj, '_doc', map); |
| 583 | } | |
| 584 | ||
| 585 | /*! | |
| 586 | * Returns an array of values from object `o`. | |
| 587 | * | |
| 588 | * @param {Object} o | |
| 589 | * @return {Array} | |
| 590 | * @private | |
| 591 | */ | |
| 592 | ||
| 593 | 1 | exports.object = {}; |
| 594 | 1 | exports.object.vals = function vals (o) { |
| 595 | 0 | var keys = Object.keys(o) |
| 596 | , i = keys.length | |
| 597 | , ret = []; | |
| 598 | ||
| 599 | 0 | while (i--) { |
| 600 | 0 | ret.push(o[keys[i]]); |
| 601 | } | |
| 602 | ||
| 603 | 0 | return ret; |
| 604 | } | |
| 605 | ||
| 606 | /*! | |
| 607 | * @see exports.options | |
| 608 | */ | |
| 609 | ||
| 610 | 1 | exports.object.shallowCopy = exports.options; |
| 611 | ||
| 612 | /*! | |
| 613 | * Safer helper for hasOwnProperty checks | |
| 614 | * | |
| 615 | * @param {Object} obj | |
| 616 | * @param {String} prop | |
| 617 | */ | |
| 618 | ||
| 619 | 1 | var hop = Object.prototype.hasOwnProperty; |
| 620 | 1 | exports.object.hasOwnProperty = function (obj, prop) { |
| 621 | 0 | return hop.call(obj, prop); |
| 622 | } | |
| 623 | ||
| 624 | /*! | |
| 625 | * Determine if `val` is null or undefined | |
| 626 | * | |
| 627 | * @return {Boolean} | |
| 628 | */ | |
| 629 | ||
| 630 | 1 | exports.isNullOrUndefined = function (val) { |
| 631 | 0 | return null == val |
| 632 | } | |
| 633 | ||
| 634 | /*! | |
| 635 | * ignore | |
| 636 | */ | |
| 637 | ||
| 638 | 1 | exports.array = {}; |
| 639 | ||
| 640 | /*! | |
| 641 | * Flattens an array. | |
| 642 | * | |
| 643 | * [ 1, [ 2, 3, [4] ]] -> [1,2,3,4] | |
| 644 | * | |
| 645 | * @param {Array} arr | |
| 646 | * @param {Function} [filter] If passed, will be invoked with each item in the array. If `filter` returns a falsey value, the item will not be included in the results. | |
| 647 | * @return {Array} | |
| 648 | * @private | |
| 649 | */ | |
| 650 | ||
| 651 | 1 | exports.array.flatten = function flatten (arr, filter, ret) { |
| 652 | 0 | ret || (ret = []); |
| 653 | ||
| 654 | 0 | arr.forEach(function (item) { |
| 655 | 0 | if (Array.isArray(item)) { |
| 656 | 0 | flatten(item, filter, ret); |
| 657 | } else { | |
| 658 | 0 | if (!filter || filter(item)) { |
| 659 | 0 | ret.push(item); |
| 660 | } | |
| 661 | } | |
| 662 | }); | |
| 663 | ||
| 664 | 0 | return ret; |
| 665 | } | |
| 666 | ||
| 667 | /*! | |
| 668 | * Determines if two buffers are equal. | |
| 669 | * | |
| 670 | * @param {Buffer} a | |
| 671 | * @param {Object} b | |
| 672 | */ | |
| 673 | ||
| 674 | 1 | exports.buffer = {}; |
| 675 | 1 | exports.buffer.areEqual = function (a, b) { |
| 676 | 0 | if (!Buffer.isBuffer(a)) return false; |
| 677 | 0 | if (!Buffer.isBuffer(b)) return false; |
| 678 | 0 | if (a.length !== b.length) return false; |
| 679 | 0 | for (var i = 0, len = a.length; i < len; ++i) { |
| 680 | 0 | if (a[i] !== b[i]) return false; |
| 681 | } | |
| 682 | 0 | return true; |
| 683 | } | |
| 684 | ||
| 685 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * VirtualType constructor | |
| 4 | * | |
| 5 | * This is what mongoose uses to define virtual attributes via `Schema.prototype.virtual`. | |
| 6 | * | |
| 7 | * ####Example: | |
| 8 | * | |
| 9 | * var fullname = schema.virtual('fullname'); | |
| 10 | * fullname instanceof mongoose.VirtualType // true | |
| 11 | * | |
| 12 | * @parma {Object} options | |
| 13 | * @api public | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | function VirtualType (options, name) { |
| 17 | 0 | this.path = name; |
| 18 | 0 | this.getters = []; |
| 19 | 0 | this.setters = []; |
| 20 | 0 | this.options = options || {}; |
| 21 | } | |
| 22 | ||
| 23 | /** | |
| 24 | * Defines a getter. | |
| 25 | * | |
| 26 | * ####Example: | |
| 27 | * | |
| 28 | * var virtual = schema.virtual('fullname'); | |
| 29 | * virtual.get(function () { | |
| 30 | * return this.name.first + ' ' + this.name.last; | |
| 31 | * }); | |
| 32 | * | |
| 33 | * @param {Function} fn | |
| 34 | * @return {VirtualType} this | |
| 35 | * @api public | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | VirtualType.prototype.get = function (fn) { |
| 39 | 0 | this.getters.push(fn); |
| 40 | 0 | return this; |
| 41 | }; | |
| 42 | ||
| 43 | /** | |
| 44 | * Defines a setter. | |
| 45 | * | |
| 46 | * ####Example: | |
| 47 | * | |
| 48 | * var virtual = schema.virtual('fullname'); | |
| 49 | * virtual.set(function (v) { | |
| 50 | * var parts = v.split(' '); | |
| 51 | * this.name.first = parts[0]; | |
| 52 | * this.name.last = parts[1]; | |
| 53 | * }); | |
| 54 | * | |
| 55 | * @param {Function} fn | |
| 56 | * @return {VirtualType} this | |
| 57 | * @api public | |
| 58 | */ | |
| 59 | ||
| 60 | 1 | VirtualType.prototype.set = function (fn) { |
| 61 | 0 | this.setters.push(fn); |
| 62 | 0 | return this; |
| 63 | }; | |
| 64 | ||
| 65 | /** | |
| 66 | * Applies getters to `value` using optional `scope`. | |
| 67 | * | |
| 68 | * @param {Object} value | |
| 69 | * @param {Object} scope | |
| 70 | * @return {any} the value after applying all getters | |
| 71 | * @api public | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | VirtualType.prototype.applyGetters = function (value, scope) { |
| 75 | 0 | var v = value; |
| 76 | 0 | for (var l = this.getters.length - 1; l >= 0; l--) { |
| 77 | 0 | v = this.getters[l].call(scope, v, this); |
| 78 | } | |
| 79 | 0 | return v; |
| 80 | }; | |
| 81 | ||
| 82 | /** | |
| 83 | * Applies setters to `value` using optional `scope`. | |
| 84 | * | |
| 85 | * @param {Object} value | |
| 86 | * @param {Object} scope | |
| 87 | * @return {any} the value after applying all setters | |
| 88 | * @api public | |
| 89 | */ | |
| 90 | ||
| 91 | 1 | VirtualType.prototype.applySetters = function (value, scope) { |
| 92 | 0 | var v = value; |
| 93 | 0 | for (var l = this.setters.length - 1; l >= 0; l--) { |
| 94 | 0 | v = this.setters[l].call(scope, v, this); |
| 95 | } | |
| 96 | 0 | return v; |
| 97 | }; | |
| 98 | ||
| 99 | /*! | |
| 100 | * exports | |
| 101 | */ | |
| 102 | ||
| 103 | 1 | module.exports = VirtualType; |
| 104 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*! | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | var Collection = require('./collection').Collection, |
| 5 | Cursor = require('./cursor').Cursor, | |
| 6 | DbCommand = require('./commands/db_command').DbCommand, | |
| 7 | utils = require('./utils'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Allows the user to access the admin functionality of MongoDB | |
| 11 | * | |
| 12 | * @class Represents the Admin methods of MongoDB. | |
| 13 | * @param {Object} db Current db instance we wish to perform Admin operations on. | |
| 14 | * @return {Function} Constructor for Admin type. | |
| 15 | */ | |
| 16 | 1 | function Admin(db) { |
| 17 | 0 | if(!(this instanceof Admin)) return new Admin(db); |
| 18 | 0 | this.db = db; |
| 19 | }; | |
| 20 | ||
| 21 | /** | |
| 22 | * Retrieve the server information for the current | |
| 23 | * instance of the db client | |
| 24 | * | |
| 25 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from buildInfo or null if an error occured. | |
| 26 | * @return {null} Returns no result | |
| 27 | * @api public | |
| 28 | */ | |
| 29 | 1 | Admin.prototype.buildInfo = function(callback) { |
| 30 | 0 | this.serverInfo(callback); |
| 31 | } | |
| 32 | ||
| 33 | /** | |
| 34 | * Retrieve the server information for the current | |
| 35 | * instance of the db client | |
| 36 | * | |
| 37 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverInfo or null if an error occured. | |
| 38 | * @return {null} Returns no result | |
| 39 | * @api private | |
| 40 | */ | |
| 41 | 1 | Admin.prototype.serverInfo = function(callback) { |
| 42 | 0 | this.db.executeDbAdminCommand({buildinfo:1}, function(err, doc) { |
| 43 | 0 | if(err != null) return callback(err, null); |
| 44 | 0 | return callback(null, doc.documents[0]); |
| 45 | }); | |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * Retrieve this db's server status. | |
| 50 | * | |
| 51 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from serverStatus or null if an error occured. | |
| 52 | * @return {null} | |
| 53 | * @api public | |
| 54 | */ | |
| 55 | 1 | Admin.prototype.serverStatus = function(callback) { |
| 56 | 0 | var self = this; |
| 57 | ||
| 58 | 0 | this.db.executeDbAdminCommand({serverStatus: 1}, function(err, doc) { |
| 59 | 0 | if(err == null && doc.documents[0].ok === 1) { |
| 60 | 0 | callback(null, doc.documents[0]); |
| 61 | } else { | |
| 62 | 0 | if(err) return callback(err, false); |
| 63 | 0 | return callback(utils.toError(doc.documents[0]), false); |
| 64 | } | |
| 65 | }); | |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Retrieve the current profiling Level for MongoDB | |
| 70 | * | |
| 71 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingLevel or null if an error occured. | |
| 72 | * @return {null} Returns no result | |
| 73 | * @api public | |
| 74 | */ | |
| 75 | 1 | Admin.prototype.profilingLevel = function(callback) { |
| 76 | 0 | var self = this; |
| 77 | ||
| 78 | 0 | this.db.executeDbAdminCommand({profile:-1}, function(err, doc) { |
| 79 | 0 | doc = doc.documents[0]; |
| 80 | ||
| 81 | 0 | if(err == null && doc.ok === 1) { |
| 82 | 0 | var was = doc.was; |
| 83 | 0 | if(was == 0) return callback(null, "off"); |
| 84 | 0 | if(was == 1) return callback(null, "slow_only"); |
| 85 | 0 | if(was == 2) return callback(null, "all"); |
| 86 | 0 | return callback(new Error("Error: illegal profiling level value " + was), null); |
| 87 | } else { | |
| 88 | 0 | err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); |
| 89 | } | |
| 90 | }); | |
| 91 | }; | |
| 92 | ||
| 93 | /** | |
| 94 | * Ping the MongoDB server and retrieve results | |
| 95 | * | |
| 96 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from ping or null if an error occured. | |
| 97 | * @return {null} Returns no result | |
| 98 | * @api public | |
| 99 | */ | |
| 100 | 1 | Admin.prototype.ping = function(options, callback) { |
| 101 | // Unpack calls | |
| 102 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 103 | 0 | callback = args.pop(); |
| 104 | ||
| 105 | 0 | this.db.executeDbAdminCommand({ping: 1}, callback); |
| 106 | } | |
| 107 | ||
| 108 | /** | |
| 109 | * Authenticate against MongoDB | |
| 110 | * | |
| 111 | * @param {String} username The user name for the authentication. | |
| 112 | * @param {String} password The password for the authentication. | |
| 113 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from authenticate or null if an error occured. | |
| 114 | * @return {null} Returns no result | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | Admin.prototype.authenticate = function(username, password, callback) { |
| 118 | 0 | this.db.authenticate(username, password, {authdb: 'admin'}, function(err, doc) { |
| 119 | 0 | return callback(err, doc); |
| 120 | }) | |
| 121 | } | |
| 122 | ||
| 123 | /** | |
| 124 | * Logout current authenticated user | |
| 125 | * | |
| 126 | * @param {Object} [options] Optional parameters to the command. | |
| 127 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from logout or null if an error occured. | |
| 128 | * @return {null} Returns no result | |
| 129 | * @api public | |
| 130 | */ | |
| 131 | 1 | Admin.prototype.logout = function(callback) { |
| 132 | 0 | this.db.logout({authdb: 'admin'}, function(err, doc) { |
| 133 | 0 | return callback(err, doc); |
| 134 | }) | |
| 135 | } | |
| 136 | ||
| 137 | /** | |
| 138 | * Add a user to the MongoDB server, if the user exists it will | |
| 139 | * overwrite the current password | |
| 140 | * | |
| 141 | * Options | |
| 142 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 143 | * | |
| 144 | * @param {String} username The user name for the authentication. | |
| 145 | * @param {String} password The password for the authentication. | |
| 146 | * @param {Object} [options] additional options during update. | |
| 147 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from addUser or null if an error occured. | |
| 148 | * @return {null} Returns no result | |
| 149 | * @api public | |
| 150 | */ | |
| 151 | 1 | Admin.prototype.addUser = function(username, password, options, callback) { |
| 152 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 153 | 0 | callback = args.pop(); |
| 154 | 0 | options = args.length ? args.shift() : {}; |
| 155 | // Set the db name to admin | |
| 156 | 0 | options.dbName = 'admin'; |
| 157 | // Add user | |
| 158 | 0 | this.db.addUser(username, password, options, function(err, doc) { |
| 159 | 0 | return callback(err, doc); |
| 160 | }) | |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Remove a user from the MongoDB server | |
| 165 | * | |
| 166 | * Options | |
| 167 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 168 | * | |
| 169 | * @param {String} username The user name for the authentication. | |
| 170 | * @param {Object} [options] additional options during update. | |
| 171 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occured. | |
| 172 | * @return {null} Returns no result | |
| 173 | * @api public | |
| 174 | */ | |
| 175 | 1 | Admin.prototype.removeUser = function(username, options, callback) { |
| 176 | 0 | var self = this; |
| 177 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 178 | 0 | callback = args.pop(); |
| 179 | 0 | options = args.length ? args.shift() : {}; |
| 180 | 0 | options.dbName = 'admin'; |
| 181 | ||
| 182 | 0 | this.db.removeUser(username, options, function(err, doc) { |
| 183 | 0 | return callback(err, doc); |
| 184 | }) | |
| 185 | } | |
| 186 | ||
| 187 | /** | |
| 188 | * Set the current profiling level of MongoDB | |
| 189 | * | |
| 190 | * @param {String} level The new profiling level (off, slow_only, all) | |
| 191 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from setProfilingLevel or null if an error occured. | |
| 192 | * @return {null} Returns no result | |
| 193 | * @api public | |
| 194 | */ | |
| 195 | 1 | Admin.prototype.setProfilingLevel = function(level, callback) { |
| 196 | 0 | var self = this; |
| 197 | 0 | var command = {}; |
| 198 | 0 | var profile = 0; |
| 199 | ||
| 200 | 0 | if(level == "off") { |
| 201 | 0 | profile = 0; |
| 202 | 0 | } else if(level == "slow_only") { |
| 203 | 0 | profile = 1; |
| 204 | 0 | } else if(level == "all") { |
| 205 | 0 | profile = 2; |
| 206 | } else { | |
| 207 | 0 | return callback(new Error("Error: illegal profiling level value " + level)); |
| 208 | } | |
| 209 | ||
| 210 | // Set up the profile number | |
| 211 | 0 | command['profile'] = profile; |
| 212 | ||
| 213 | 0 | this.db.executeDbAdminCommand(command, function(err, doc) { |
| 214 | 0 | doc = doc.documents[0]; |
| 215 | ||
| 216 | 0 | if(err == null && doc.ok === 1) |
| 217 | 0 | return callback(null, level); |
| 218 | 0 | return err != null ? callback(err, null) : callback(new Error("Error with profile command"), null); |
| 219 | }); | |
| 220 | }; | |
| 221 | ||
| 222 | /** | |
| 223 | * Retrive the current profiling information for MongoDB | |
| 224 | * | |
| 225 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from profilingInfo or null if an error occured. | |
| 226 | * @return {null} Returns no result | |
| 227 | * @api public | |
| 228 | */ | |
| 229 | 1 | Admin.prototype.profilingInfo = function(callback) { |
| 230 | 0 | try { |
| 231 | 0 | new Cursor(this.db, new Collection(this.db, DbCommand.SYSTEM_PROFILE_COLLECTION), {}, {}, {dbName: 'admin'}).toArray(function(err, items) { |
| 232 | 0 | return callback(err, items); |
| 233 | }); | |
| 234 | } catch (err) { | |
| 235 | 0 | return callback(err, null); |
| 236 | } | |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Execute a db command against the Admin database | |
| 241 | * | |
| 242 | * @param {Object} command A command object `{ping:1}`. | |
| 243 | * @param {Object} [options] Optional parameters to the command. | |
| 244 | * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. | |
| 245 | * @return {null} Returns no result | |
| 246 | * @api public | |
| 247 | */ | |
| 248 | 1 | Admin.prototype.command = function(command, options, callback) { |
| 249 | 0 | var self = this; |
| 250 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 251 | 0 | callback = args.pop(); |
| 252 | 0 | options = args.length ? args.shift() : {}; |
| 253 | ||
| 254 | // Execute a command | |
| 255 | 0 | this.db.executeDbAdminCommand(command, options, function(err, doc) { |
| 256 | // Ensure change before event loop executes | |
| 257 | 0 | return callback != null ? callback(err, doc) : null; |
| 258 | }); | |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Validate an existing collection | |
| 263 | * | |
| 264 | * @param {String} collectionName The name of the collection to validate. | |
| 265 | * @param {Object} [options] Optional parameters to the command. | |
| 266 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from validateCollection or null if an error occured. | |
| 267 | * @return {null} Returns no result | |
| 268 | * @api public | |
| 269 | */ | |
| 270 | 1 | Admin.prototype.validateCollection = function(collectionName, options, callback) { |
| 271 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 272 | 0 | callback = args.pop(); |
| 273 | 0 | options = args.length ? args.shift() : {}; |
| 274 | ||
| 275 | 0 | var self = this; |
| 276 | 0 | var command = {validate: collectionName}; |
| 277 | 0 | var keys = Object.keys(options); |
| 278 | ||
| 279 | // Decorate command with extra options | |
| 280 | 0 | for(var i = 0; i < keys.length; i++) { |
| 281 | 0 | if(options.hasOwnProperty(keys[i])) { |
| 282 | 0 | command[keys[i]] = options[keys[i]]; |
| 283 | } | |
| 284 | } | |
| 285 | ||
| 286 | 0 | this.db.executeDbCommand(command, function(err, doc) { |
| 287 | 0 | if(err != null) return callback(err, null); |
| 288 | 0 | doc = doc.documents[0]; |
| 289 | ||
| 290 | 0 | if(doc.ok === 0) |
| 291 | 0 | return callback(new Error("Error with validate command"), null); |
| 292 | 0 | if(doc.result != null && doc.result.constructor != String) |
| 293 | 0 | return callback(new Error("Error with validation data"), null); |
| 294 | 0 | if(doc.result != null && doc.result.match(/exception|corrupt/) != null) |
| 295 | 0 | return callback(new Error("Error: invalid collection " + collectionName), null); |
| 296 | 0 | if(doc.valid != null && !doc.valid) |
| 297 | 0 | return callback(new Error("Error: invalid collection " + collectionName), null); |
| 298 | ||
| 299 | 0 | return callback(null, doc); |
| 300 | }); | |
| 301 | }; | |
| 302 | ||
| 303 | /** | |
| 304 | * List the available databases | |
| 305 | * | |
| 306 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from listDatabases or null if an error occured. | |
| 307 | * @return {null} Returns no result | |
| 308 | * @api public | |
| 309 | */ | |
| 310 | 1 | Admin.prototype.listDatabases = function(callback) { |
| 311 | // Execute the listAllDatabases command | |
| 312 | 0 | this.db.executeDbAdminCommand({listDatabases:1}, {}, function(err, doc) { |
| 313 | 0 | if(err != null) return callback(err, null); |
| 314 | 0 | return callback(null, doc.documents[0]); |
| 315 | }); | |
| 316 | } | |
| 317 | ||
| 318 | /** | |
| 319 | * Get ReplicaSet status | |
| 320 | * | |
| 321 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from replSetGetStatus or null if an error occured. | |
| 322 | * @return {null} | |
| 323 | * @api public | |
| 324 | */ | |
| 325 | 1 | Admin.prototype.replSetGetStatus = function(callback) { |
| 326 | 0 | var self = this; |
| 327 | ||
| 328 | 0 | this.db.executeDbAdminCommand({replSetGetStatus:1}, function(err, doc) { |
| 329 | 0 | if(err == null && doc.documents[0].ok === 1) |
| 330 | 0 | return callback(null, doc.documents[0]); |
| 331 | 0 | if(err) return callback(err, false); |
| 332 | 0 | return callback(utils.toError(doc.documents[0]), false); |
| 333 | }); | |
| 334 | }; | |
| 335 | ||
| 336 | /** | |
| 337 | * @ignore | |
| 338 | */ | |
| 339 | 1 | exports.Admin = Admin; |
| 340 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('./connection/read_preference').ReadPreference |
| 2 | , Readable = require('stream').Readable | |
| 3 | , CommandCursor = require('./command_cursor').CommandCursor | |
| 4 | , utils = require('./utils') | |
| 5 | , shared = require('./collection/shared') | |
| 6 | , inherits = require('util').inherits; | |
| 7 | ||
| 8 | 1 | var AggregationCursor = function(collection, serverCapabilities, options) { |
| 9 | 0 | var pipe = []; |
| 10 | 0 | var self = this; |
| 11 | 0 | var results = null; |
| 12 | 0 | var _cursor_options = {}; |
| 13 | // Ensure we have options set up | |
| 14 | 0 | options = options == null ? {} : options; |
| 15 | ||
| 16 | // If a pipeline was provided | |
| 17 | 0 | pipe = Array.isArray(options.pipe) ? options.pipe : pipe; |
| 18 | // Set passed in batchSize if provided | |
| 19 | 0 | if(typeof options.batchSize == 'number') _cursor_options.batchSize = options.batchSize; |
| 20 | // Get the read Preference | |
| 21 | 0 | var readPreference = shared._getReadConcern(collection, options); |
| 22 | ||
| 23 | // Set up | |
| 24 | 0 | Readable.call(this, {objectMode: true}); |
| 25 | ||
| 26 | // Contains connection | |
| 27 | 0 | var connection = null; |
| 28 | ||
| 29 | // Set the read preference | |
| 30 | 0 | var _options = { |
| 31 | readPreference: readPreference | |
| 32 | }; | |
| 33 | ||
| 34 | // Actual command | |
| 35 | 0 | var command = { |
| 36 | aggregate: collection.collectionName | |
| 37 | , pipeline: pipe | |
| 38 | , cursor: _cursor_options | |
| 39 | } | |
| 40 | ||
| 41 | // If allowDiskUsage is set | |
| 42 | 0 | if(typeof options.allowDiskUsage == 'boolean') |
| 43 | 0 | command.allowDiskUsage = options.allowDiskUsage; |
| 44 | ||
| 45 | // Command cursor (if we support one) | |
| 46 | 0 | var commandCursor = new CommandCursor(collection.db, collection, command); |
| 47 | ||
| 48 | // // Internal cursor methods | |
| 49 | // this.find = function(selector) { | |
| 50 | // pipe.push({$match: selector}); | |
| 51 | // return self; | |
| 52 | // } | |
| 53 | ||
| 54 | // this.unwind = function(unwind) { | |
| 55 | // pipe.push({$unwind: unwind}); | |
| 56 | // return self; | |
| 57 | // } | |
| 58 | ||
| 59 | // this.group = function(group) { | |
| 60 | // pipe.push({$group: group}); | |
| 61 | // return self; | |
| 62 | // } | |
| 63 | ||
| 64 | // this.project = function(project) { | |
| 65 | // pipe.push({$project: project}); | |
| 66 | // return self; | |
| 67 | // } | |
| 68 | ||
| 69 | // this.limit = function(limit) { | |
| 70 | // pipe.push({$limit: limit}); | |
| 71 | // return self; | |
| 72 | // } | |
| 73 | ||
| 74 | // this.geoNear = function(geoNear) { | |
| 75 | // pipe.push({$geoNear: geoNear}); | |
| 76 | // return self; | |
| 77 | // } | |
| 78 | ||
| 79 | // this.sort = function(sort) { | |
| 80 | // pipe.push({$sort: sort}); | |
| 81 | // return self; | |
| 82 | // } | |
| 83 | ||
| 84 | // this.withReadPreference = function(read_preference) { | |
| 85 | // _options.readPreference = read_preference; | |
| 86 | // return self; | |
| 87 | // } | |
| 88 | ||
| 89 | // this.withQueryOptions = function(options) { | |
| 90 | // if(options.batchSize) { | |
| 91 | // _cursor_options.batchSize = options.batchSize; | |
| 92 | // } | |
| 93 | ||
| 94 | // // Return the cursor | |
| 95 | // return self; | |
| 96 | // } | |
| 97 | ||
| 98 | // this.skip = function(skip) { | |
| 99 | // pipe.push({$skip: skip}); | |
| 100 | // return self; | |
| 101 | // } | |
| 102 | ||
| 103 | // this.allowDiskUsage = function(allowDiskUsage) { | |
| 104 | // command.allowDiskUsage = allowDiskUsage; | |
| 105 | // return self; | |
| 106 | // } | |
| 107 | ||
| 108 | 0 | this.explain = function(callback) { |
| 109 | 0 | if(typeof callback != 'function') |
| 110 | 0 | throw utils.toError("AggregationCursor explain requires a callback function"); |
| 111 | ||
| 112 | // Add explain options | |
| 113 | 0 | _options.explain = true; |
| 114 | // Execute aggregation pipeline | |
| 115 | 0 | collection.aggregate(pipe, _options, function(err, results) { |
| 116 | 0 | if(err) return callback(err, null); |
| 117 | 0 | callback(null, results); |
| 118 | }); | |
| 119 | } | |
| 120 | ||
| 121 | // this.maxTimeMS = function(maxTimeMS) { | |
| 122 | // if(typeof maxTimeMS != 'number') { | |
| 123 | // throw new Error("maxTimeMS must be a number"); | |
| 124 | // } | |
| 125 | ||
| 126 | // // Save the maxTimeMS | |
| 127 | // _options.maxTimeMS = maxTimeMS | |
| 128 | // // Set the maxTimeMS on the command cursor | |
| 129 | // commandCursor.maxTimeMS(maxTimeMS); | |
| 130 | // return self; | |
| 131 | // } | |
| 132 | ||
| 133 | 0 | this.get = function(callback) { |
| 134 | 0 | if(typeof callback != 'function') |
| 135 | 0 | throw utils.toError("AggregationCursor get requires a callback function"); |
| 136 | // Checkout a connection | |
| 137 | 0 | var _connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 138 | // Fall back | |
| 139 | 0 | if(!_connection.serverCapabilities.hasAggregationCursor) { |
| 140 | 0 | return collection.aggregate(pipe, _options, function(err, results) { |
| 141 | 0 | if(err) return callback(err); |
| 142 | 0 | callback(null, results); |
| 143 | }); | |
| 144 | } | |
| 145 | ||
| 146 | // Execute get using command Cursor | |
| 147 | 0 | commandCursor.get({connection: _connection}, callback); |
| 148 | } | |
| 149 | ||
| 150 | 0 | this.getOne = function(callback) { |
| 151 | 0 | if(typeof callback != 'function') |
| 152 | 0 | throw utils.toError("AggregationCursor getOne requires a callback function"); |
| 153 | // Set the limit to 1 | |
| 154 | 0 | pipe.push({$limit: 1}); |
| 155 | // For now we have no cursor command so let's just wrap existing results | |
| 156 | 0 | collection.aggregate(pipe, _options, function(err, results) { |
| 157 | 0 | if(err) return callback(err); |
| 158 | 0 | callback(null, results[0]); |
| 159 | }); | |
| 160 | } | |
| 161 | ||
| 162 | 0 | this.each = function(callback) { |
| 163 | // Checkout a connection if we have none | |
| 164 | 0 | if(!connection) |
| 165 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 166 | ||
| 167 | // Fall back | |
| 168 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 169 | 0 | collection.aggregate(pipe, _options, function(err, _results) { |
| 170 | 0 | if(err) return callback(err); |
| 171 | ||
| 172 | 0 | while(_results.length > 0) { |
| 173 | 0 | callback(null, _results.shift()); |
| 174 | } | |
| 175 | ||
| 176 | 0 | callback(null, null); |
| 177 | }); | |
| 178 | } | |
| 179 | ||
| 180 | // Execute each using command Cursor | |
| 181 | 0 | commandCursor.each({connection: connection}, callback); |
| 182 | } | |
| 183 | ||
| 184 | 0 | this.next = function(callback) { |
| 185 | 0 | if(typeof callback != 'function') |
| 186 | 0 | throw utils.toError("AggregationCursor next requires a callback function"); |
| 187 | ||
| 188 | // Checkout a connection if we have none | |
| 189 | 0 | if(!connection) |
| 190 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 191 | ||
| 192 | // Fall back | |
| 193 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 194 | 0 | if(!results) { |
| 195 | // For now we have no cursor command so let's just wrap existing results | |
| 196 | 0 | return collection.aggregate(pipe, _options, function(err, _results) { |
| 197 | 0 | if(err) return callback(err); |
| 198 | 0 | results = _results; |
| 199 | ||
| 200 | // Ensure we don't issue undefined | |
| 201 | 0 | var item = results.shift(); |
| 202 | 0 | callback(null, item ? item : null); |
| 203 | }); | |
| 204 | } | |
| 205 | ||
| 206 | // Ensure we don't issue undefined | |
| 207 | 0 | var item = results.shift(); |
| 208 | // Return the item | |
| 209 | 0 | return callback(null, item ? item : null); |
| 210 | } | |
| 211 | ||
| 212 | // Execute next using command Cursor | |
| 213 | 0 | commandCursor.next({connection: connection}, callback); |
| 214 | } | |
| 215 | ||
| 216 | // | |
| 217 | // Close method | |
| 218 | // | |
| 219 | 0 | this.close = function(callback) { |
| 220 | 0 | if(typeof callback != 'function') |
| 221 | 0 | throw utils.toError("AggregationCursor close requires a callback function"); |
| 222 | ||
| 223 | // Checkout a connection if we have none | |
| 224 | 0 | if(!connection) |
| 225 | 0 | connection = collection.db.serverConfig.checkoutReader(_options.readPreference); |
| 226 | ||
| 227 | // Fall back | |
| 228 | 0 | if(!connection.serverCapabilities.hasAggregationCursor) { |
| 229 | 0 | return callback(null, null); |
| 230 | } | |
| 231 | ||
| 232 | // Execute next using command Cursor | |
| 233 | 0 | commandCursor.close({connection: connection}, callback); |
| 234 | } | |
| 235 | ||
| 236 | // | |
| 237 | // Stream method | |
| 238 | // | |
| 239 | 0 | this._read = function(n) { |
| 240 | 0 | self.next(function(err, result) { |
| 241 | 0 | if(err) { |
| 242 | 0 | self.emit('error', err); |
| 243 | 0 | return self.push(null); |
| 244 | } | |
| 245 | ||
| 246 | 0 | self.push(result); |
| 247 | }); | |
| 248 | } | |
| 249 | } | |
| 250 | ||
| 251 | // Inherit from Readable | |
| 252 | 1 | if(Readable != null) { |
| 253 | 1 | inherits(AggregationCursor, Readable); |
| 254 | } | |
| 255 | ||
| 256 | // Exports the Aggregation Framework | |
| 257 | 1 | exports.AggregationCursor = AggregationCursor; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils'); | |
| 3 | ||
| 4 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 5 | 0 | var numberOfConnections = 0; |
| 6 | 0 | var errorObject = null; |
| 7 | ||
| 8 | 0 | if(options['connection'] != null) { |
| 9 | //if a connection was explicitly passed on options, then we have only one... | |
| 10 | 0 | numberOfConnections = 1; |
| 11 | } else { | |
| 12 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 13 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 14 | 0 | options['onAll'] = true; |
| 15 | } | |
| 16 | ||
| 17 | // Execute all four | |
| 18 | 0 | db._executeQueryCommand(DbCommand.createGetNonceCommand(db), options, function(err, result, connection) { |
| 19 | // console.log("--------------------------------------------- MONGODB-CR 0") | |
| 20 | // console.dir(err) | |
| 21 | // Execute on all the connections | |
| 22 | 0 | if(err == null) { |
| 23 | // Nonce used to make authentication request with md5 hash | |
| 24 | 0 | var nonce = result.documents[0].nonce; |
| 25 | // Execute command | |
| 26 | 0 | db._executeQueryCommand(DbCommand.createAuthenticationCommand(db, username, password, nonce, authdb), {connection:connection}, function(err, result) { |
| 27 | // console.log("--------------------------------------------- MONGODB-CR 1") | |
| 28 | // console.dir(err) | |
| 29 | // console.dir(result) | |
| 30 | // Count down | |
| 31 | 0 | numberOfConnections = numberOfConnections - 1; |
| 32 | // Ensure we save any error | |
| 33 | 0 | if(err) { |
| 34 | 0 | errorObject = err; |
| 35 | 0 | } else if(result |
| 36 | && Array.isArray(result.documents) | |
| 37 | && result.documents.length > 0 | |
| 38 | && (result.documents[0].err != null || result.documents[0].errmsg != null)) { | |
| 39 | 0 | errorObject = utils.toError(result.documents[0]); |
| 40 | } | |
| 41 | ||
| 42 | // Work around the case where the number of connections are 0 | |
| 43 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 44 | 0 | var internalCallback = callback; |
| 45 | 0 | callback = null; |
| 46 | ||
| 47 | 0 | if(errorObject == null |
| 48 | && result && Array.isArray(result.documents) && result.documents.length > 0 | |
| 49 | && result.documents[0].ok == 1) { // We authenticated correctly save the credentials | |
| 50 | 0 | db.serverConfig.auth.add('MONGODB-CR', db.databaseName, username, password, authdb); |
| 51 | // Return callback | |
| 52 | 0 | internalCallback(errorObject, true); |
| 53 | } else { | |
| 54 | 0 | internalCallback(errorObject, false); |
| 55 | } | |
| 56 | } | |
| 57 | }); | |
| 58 | } | |
| 59 | }); | |
| 60 | } | |
| 61 | ||
| 62 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , format = require('util').format; | |
| 4 | ||
| 5 | // Kerberos class | |
| 6 | 1 | var Kerberos = null; |
| 7 | 1 | var MongoAuthProcess = null; |
| 8 | // Try to grab the Kerberos class | |
| 9 | 1 | try { |
| 10 | 1 | Kerberos = require('kerberos').Kerberos |
| 11 | // Authentication process for Mongo | |
| 12 | 1 | MongoAuthProcess = require('kerberos').processes.MongoAuthProcess |
| 13 | } catch(err) {} | |
| 14 | ||
| 15 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 16 | 0 | var numberOfConnections = 0; |
| 17 | 0 | var errorObject = null; |
| 18 | // We don't have the Kerberos library | |
| 19 | 0 | if(Kerberos == null) return callback(new Error("Kerberos library is not installed")); |
| 20 | ||
| 21 | 0 | if(options['connection'] != null) { |
| 22 | //if a connection was explicitly passed on options, then we have only one... | |
| 23 | 0 | numberOfConnections = 1; |
| 24 | } else { | |
| 25 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 26 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 27 | 0 | options['onAll'] = true; |
| 28 | } | |
| 29 | ||
| 30 | // Grab all the connections | |
| 31 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 32 | 0 | var gssapiServiceName = options['gssapiServiceName'] || 'mongodb'; |
| 33 | 0 | var error = null; |
| 34 | // Authenticate all connections | |
| 35 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 36 | ||
| 37 | // Start Auth process for a connection | |
| 38 | 0 | GSSAPIInitialize(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) { |
| 39 | // Adjust number of connections left to connect | |
| 40 | 0 | numberOfConnections = numberOfConnections - 1; |
| 41 | // If we have an error save it | |
| 42 | 0 | if(err) error = err; |
| 43 | ||
| 44 | // We are done | |
| 45 | 0 | if(numberOfConnections == 0) { |
| 46 | 0 | if(err) return callback(error, false); |
| 47 | // We authenticated correctly save the credentials | |
| 48 | 0 | db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName); |
| 49 | // Return valid callback | |
| 50 | 0 | return callback(null, true); |
| 51 | } | |
| 52 | }); | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | // | |
| 57 | // Initialize step | |
| 58 | 1 | var GSSAPIInitialize = function(db, username, password, authdb, gssapiServiceName, connection, callback) { |
| 59 | // Create authenticator | |
| 60 | 0 | var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, gssapiServiceName); |
| 61 | ||
| 62 | // Perform initialization | |
| 63 | 0 | mongo_auth_process.init(username, password, function(err, context) { |
| 64 | 0 | if(err) return callback(err, false); |
| 65 | ||
| 66 | // Perform the first step | |
| 67 | 0 | mongo_auth_process.transition('', function(err, payload) { |
| 68 | 0 | if(err) return callback(err, false); |
| 69 | ||
| 70 | // Call the next db step | |
| 71 | 0 | MongoDBGSSAPIFirstStep(mongo_auth_process, payload, db, username, password, authdb, connection, callback); |
| 72 | }); | |
| 73 | }); | |
| 74 | } | |
| 75 | ||
| 76 | // | |
| 77 | // Perform first step against mongodb | |
| 78 | 1 | var MongoDBGSSAPIFirstStep = function(mongo_auth_process, payload, db, username, password, authdb, connection, callback) { |
| 79 | // Build the sasl start command | |
| 80 | 0 | var command = { |
| 81 | saslStart: 1 | |
| 82 | , mechanism: 'GSSAPI' | |
| 83 | , payload: payload | |
| 84 | , autoAuthorize: 1 | |
| 85 | }; | |
| 86 | ||
| 87 | // Execute first sasl step | |
| 88 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 89 | 0 | if(err) return callback(err, false); |
| 90 | // Get the payload | |
| 91 | 0 | doc = doc.documents[0]; |
| 92 | 0 | var db_payload = doc.payload; |
| 93 | ||
| 94 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 95 | 0 | if(err) return callback(err, false); |
| 96 | ||
| 97 | // MongoDB API Second Step | |
| 98 | 0 | MongoDBGSSAPISecondStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback); |
| 99 | }); | |
| 100 | }); | |
| 101 | } | |
| 102 | ||
| 103 | // | |
| 104 | // Perform first step against mongodb | |
| 105 | 1 | var MongoDBGSSAPISecondStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) { |
| 106 | // Build Authentication command to send to MongoDB | |
| 107 | 0 | var command = { |
| 108 | saslContinue: 1 | |
| 109 | , conversationId: doc.conversationId | |
| 110 | , payload: payload | |
| 111 | }; | |
| 112 | ||
| 113 | // Execute the command | |
| 114 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 115 | 0 | if(err) return callback(err, false); |
| 116 | ||
| 117 | // Get the result document | |
| 118 | 0 | doc = doc.documents[0]; |
| 119 | ||
| 120 | // Call next transition for kerberos | |
| 121 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 122 | 0 | if(err) return callback(err, false); |
| 123 | ||
| 124 | // Call the last and third step | |
| 125 | 0 | MongoDBGSSAPIThirdStep(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback); |
| 126 | }); | |
| 127 | }); | |
| 128 | } | |
| 129 | ||
| 130 | 1 | var MongoDBGSSAPIThirdStep = function(mongo_auth_process, payload, doc, db, username, password, authdb, connection, callback) { |
| 131 | // Build final command | |
| 132 | 0 | var command = { |
| 133 | saslContinue: 1 | |
| 134 | , conversationId: doc.conversationId | |
| 135 | , payload: payload | |
| 136 | }; | |
| 137 | ||
| 138 | // Let's finish the auth process against mongodb | |
| 139 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 140 | 0 | if(err) return callback(err, false); |
| 141 | ||
| 142 | 0 | mongo_auth_process.transition(null, function(err, payload) { |
| 143 | 0 | if(err) return callback(err, false); |
| 144 | 0 | callback(null, true); |
| 145 | }); | |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , Binary = require('bson').Binary | |
| 4 | , format = require('util').format; | |
| 5 | ||
| 6 | 1 | var authenticate = function(db, username, password, options, callback) { |
| 7 | 0 | var numberOfConnections = 0; |
| 8 | 0 | var errorObject = null; |
| 9 | ||
| 10 | 0 | if(options['connection'] != null) { |
| 11 | //if a connection was explicitly passed on options, then we have only one... | |
| 12 | 0 | numberOfConnections = 1; |
| 13 | } else { | |
| 14 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 15 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 16 | 0 | options['onAll'] = true; |
| 17 | } | |
| 18 | ||
| 19 | // Create payload | |
| 20 | 0 | var payload = new Binary(format("\x00%s\x00%s", username, password)); |
| 21 | ||
| 22 | // Let's start the sasl process | |
| 23 | 0 | var command = { |
| 24 | saslStart: 1 | |
| 25 | , mechanism: 'PLAIN' | |
| 26 | , payload: payload | |
| 27 | , autoAuthorize: 1 | |
| 28 | }; | |
| 29 | ||
| 30 | // Grab all the connections | |
| 31 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 32 | ||
| 33 | // Authenticate all connections | |
| 34 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 35 | 0 | var connection = connections[i]; |
| 36 | // Execute first sasl step | |
| 37 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, result) { |
| 38 | // Count down | |
| 39 | 0 | numberOfConnections = numberOfConnections - 1; |
| 40 | ||
| 41 | // Ensure we save any error | |
| 42 | 0 | if(err) { |
| 43 | 0 | errorObject = err; |
| 44 | 0 | } else if(result.documents[0].err != null || result.documents[0].errmsg != null){ |
| 45 | 0 | errorObject = utils.toError(result.documents[0]); |
| 46 | } | |
| 47 | ||
| 48 | // Work around the case where the number of connections are 0 | |
| 49 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 50 | 0 | var internalCallback = callback; |
| 51 | 0 | callback = null; |
| 52 | ||
| 53 | 0 | if(errorObject == null && result.documents[0].ok == 1) { |
| 54 | // We authenticated correctly save the credentials | |
| 55 | 0 | db.serverConfig.auth.add('PLAIN', db.databaseName, username, password); |
| 56 | // Return callback | |
| 57 | 0 | internalCallback(errorObject, true); |
| 58 | } else { | |
| 59 | 0 | internalCallback(errorObject, false); |
| 60 | } | |
| 61 | } | |
| 62 | }); | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , format = require('util').format; | |
| 4 | ||
| 5 | // Kerberos class | |
| 6 | 1 | var Kerberos = null; |
| 7 | 1 | var MongoAuthProcess = null; |
| 8 | // Try to grab the Kerberos class | |
| 9 | 1 | try { |
| 10 | 1 | Kerberos = require('kerberos').Kerberos |
| 11 | // Authentication process for Mongo | |
| 12 | 1 | MongoAuthProcess = require('kerberos').processes.MongoAuthProcess |
| 13 | } catch(err) {} | |
| 14 | ||
| 15 | 1 | var authenticate = function(db, username, password, authdb, options, callback) { |
| 16 | 0 | var numberOfConnections = 0; |
| 17 | 0 | var errorObject = null; |
| 18 | // We don't have the Kerberos library | |
| 19 | 0 | if(Kerberos == null) return callback(new Error("Kerberos library is not installed")); |
| 20 | ||
| 21 | 0 | if(options['connection'] != null) { |
| 22 | //if a connection was explicitly passed on options, then we have only one... | |
| 23 | 0 | numberOfConnections = 1; |
| 24 | } else { | |
| 25 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 26 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 27 | 0 | options['onAll'] = true; |
| 28 | } | |
| 29 | ||
| 30 | // Set the sspi server name | |
| 31 | 0 | var gssapiServiceName = options['gssapiServiceName'] || 'mongodb'; |
| 32 | ||
| 33 | // Grab all the connections | |
| 34 | 0 | var connections = db.serverConfig.allRawConnections(); |
| 35 | 0 | var error = null; |
| 36 | ||
| 37 | // Authenticate all connections | |
| 38 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 39 | // Start Auth process for a connection | |
| 40 | 0 | SSIPAuthenticate(db, username, password, authdb, gssapiServiceName, connections[i], function(err, result) { |
| 41 | // Adjust number of connections left to connect | |
| 42 | 0 | numberOfConnections = numberOfConnections - 1; |
| 43 | // If we have an error save it | |
| 44 | 0 | if(err) error = err; |
| 45 | ||
| 46 | // We are done | |
| 47 | 0 | if(numberOfConnections == 0) { |
| 48 | 0 | if(err) return callback(err, false); |
| 49 | // We authenticated correctly save the credentials | |
| 50 | 0 | db.serverConfig.auth.add('GSSAPI', db.databaseName, username, password, authdb, gssapiServiceName); |
| 51 | // Return valid callback | |
| 52 | 0 | return callback(null, true); |
| 53 | } | |
| 54 | }); | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | 1 | var SSIPAuthenticate = function(db, username, password, authdb, service_name, connection, callback) { |
| 59 | // -------------------------------------------------------------- | |
| 60 | // Async Version | |
| 61 | // -------------------------------------------------------------- | |
| 62 | 0 | var command = { |
| 63 | saslStart: 1 | |
| 64 | , mechanism: 'GSSAPI' | |
| 65 | , payload: '' | |
| 66 | , autoAuthorize: 1 | |
| 67 | }; | |
| 68 | ||
| 69 | // Create authenticator | |
| 70 | 0 | var mongo_auth_process = new MongoAuthProcess(connection.socketOptions.host, connection.socketOptions.port, service_name); |
| 71 | ||
| 72 | // Execute first sasl step | |
| 73 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 74 | 0 | if(err) return callback(err); |
| 75 | 0 | doc = doc.documents[0]; |
| 76 | ||
| 77 | 0 | mongo_auth_process.init(username, password, function(err) { |
| 78 | 0 | if(err) return callback(err); |
| 79 | ||
| 80 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 81 | 0 | if(err) return callback(err); |
| 82 | ||
| 83 | // Perform the next step against mongod | |
| 84 | 0 | var command = { |
| 85 | saslContinue: 1 | |
| 86 | , conversationId: doc.conversationId | |
| 87 | , payload: payload | |
| 88 | }; | |
| 89 | ||
| 90 | // Execute the command | |
| 91 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 92 | 0 | if(err) return callback(err); |
| 93 | 0 | doc = doc.documents[0]; |
| 94 | ||
| 95 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 96 | 0 | if(err) return callback(err); |
| 97 | ||
| 98 | // Perform the next step against mongod | |
| 99 | 0 | var command = { |
| 100 | saslContinue: 1 | |
| 101 | , conversationId: doc.conversationId | |
| 102 | , payload: payload | |
| 103 | }; | |
| 104 | ||
| 105 | // Execute the command | |
| 106 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 107 | 0 | if(err) return callback(err); |
| 108 | 0 | doc = doc.documents[0]; |
| 109 | ||
| 110 | 0 | mongo_auth_process.transition(doc.payload, function(err, payload) { |
| 111 | // Perform the next step against mongod | |
| 112 | 0 | var command = { |
| 113 | saslContinue: 1 | |
| 114 | , conversationId: doc.conversationId | |
| 115 | , payload: payload | |
| 116 | }; | |
| 117 | ||
| 118 | // Execute the command | |
| 119 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, doc) { |
| 120 | 0 | if(err) return callback(err); |
| 121 | 0 | doc = doc.documents[0]; |
| 122 | ||
| 123 | 0 | if(doc.done) return callback(null, true); |
| 124 | 0 | callback(new Error("Authentication failed"), false); |
| 125 | }); | |
| 126 | }); | |
| 127 | }); | |
| 128 | }); | |
| 129 | }); | |
| 130 | }); | |
| 131 | }); | |
| 132 | }); | |
| 133 | } | |
| 134 | ||
| 135 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../commands/db_command').DbCommand |
| 2 | , utils = require('../utils') | |
| 3 | , Binary = require('bson').Binary | |
| 4 | , format = require('util').format; | |
| 5 | ||
| 6 | 1 | var authenticate = function(db, username, password, options, callback) { |
| 7 | 0 | var numberOfConnections = 0; |
| 8 | 0 | var errorObject = null; |
| 9 | ||
| 10 | 0 | if(options['connection'] != null) { |
| 11 | //if a connection was explicitly passed on options, then we have only one... | |
| 12 | 0 | numberOfConnections = 1; |
| 13 | } else { | |
| 14 | // Get the amount of connections in the pool to ensure we have authenticated all comments | |
| 15 | 0 | numberOfConnections = db.serverConfig.allRawConnections().length; |
| 16 | 0 | options['onAll'] = true; |
| 17 | } | |
| 18 | ||
| 19 | // Let's start the sasl process | |
| 20 | 0 | var command = { |
| 21 | authenticate: 1 | |
| 22 | , mechanism: 'MONGODB-X509' | |
| 23 | , user: username | |
| 24 | }; | |
| 25 | ||
| 26 | // Grab all the connections | |
| 27 | 0 | var connections = options['connection'] != null ? [options['connection']] : db.serverConfig.allRawConnections(); |
| 28 | ||
| 29 | // Authenticate all connections | |
| 30 | 0 | for(var i = 0; i < numberOfConnections; i++) { |
| 31 | 0 | var connection = connections[i]; |
| 32 | // Execute first sasl step | |
| 33 | 0 | db._executeQueryCommand(DbCommand.createDbCommand(db, command, {}, '$external'), {connection:connection}, function(err, result) { |
| 34 | // Count down | |
| 35 | 0 | numberOfConnections = numberOfConnections - 1; |
| 36 | ||
| 37 | // Ensure we save any error | |
| 38 | 0 | if(err) { |
| 39 | 0 | errorObject = err; |
| 40 | 0 | } else if(result.documents[0].err != null || result.documents[0].errmsg != null){ |
| 41 | 0 | errorObject = utils.toError(result.documents[0]); |
| 42 | } | |
| 43 | ||
| 44 | // Work around the case where the number of connections are 0 | |
| 45 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 46 | 0 | var internalCallback = callback; |
| 47 | 0 | callback = null; |
| 48 | ||
| 49 | 0 | if(errorObject == null && result.documents[0].ok == 1) { |
| 50 | // We authenticated correctly save the credentials | |
| 51 | 0 | db.serverConfig.auth.add('MONGODB-X509', db.databaseName, username, password); |
| 52 | // Return callback | |
| 53 | 0 | internalCallback(errorObject, true); |
| 54 | } else { | |
| 55 | 0 | internalCallback(errorObject, false); |
| 56 | } | |
| 57 | } | |
| 58 | }); | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | 1 | exports.authenticate = authenticate; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | * @ignore | |
| 4 | */ | |
| 5 | 1 | var InsertCommand = require('./commands/insert_command').InsertCommand |
| 6 | , QueryCommand = require('./commands/query_command').QueryCommand | |
| 7 | , DeleteCommand = require('./commands/delete_command').DeleteCommand | |
| 8 | , UpdateCommand = require('./commands/update_command').UpdateCommand | |
| 9 | , DbCommand = require('./commands/db_command').DbCommand | |
| 10 | , ObjectID = require('bson').ObjectID | |
| 11 | , Code = require('bson').Code | |
| 12 | , Cursor = require('./cursor').Cursor | |
| 13 | , utils = require('./utils') | |
| 14 | , shared = require('./collection/shared') | |
| 15 | , core = require('./collection/core') | |
| 16 | , query = require('./collection/query') | |
| 17 | , index = require('./collection/index') | |
| 18 | , geo = require('./collection/geo') | |
| 19 | , commands = require('./collection/commands') | |
| 20 | , aggregation = require('./collection/aggregation'); | |
| 21 | ||
| 22 | /** | |
| 23 | * Create a new Collection instance (INTERNAL TYPE, do not instantiate directly) | |
| 24 | * | |
| 25 | * Options | |
| 26 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 27 | * - **slaveOk** {Boolean, default:false}, Allow reads from secondaries. | |
| 28 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 29 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 30 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 31 | * | |
| 32 | * @class Represents a Collection | |
| 33 | * @param {Object} db db instance. | |
| 34 | * @param {String} collectionName collection name. | |
| 35 | * @param {Object} [pkFactory] alternative primary key factory. | |
| 36 | * @param {Object} [options] additional options for the collection. | |
| 37 | * @return {Object} a collection instance. | |
| 38 | */ | |
| 39 | 1 | function Collection (db, collectionName, pkFactory, options) { |
| 40 | 0 | if(!(this instanceof Collection)) return new Collection(db, collectionName, pkFactory, options); |
| 41 | ||
| 42 | 0 | shared.checkCollectionName(collectionName); |
| 43 | ||
| 44 | 0 | this.db = db; |
| 45 | 0 | this.collectionName = collectionName; |
| 46 | 0 | this.internalHint = null; |
| 47 | 0 | this.opts = options != null && ('object' === typeof options) ? options : {}; |
| 48 | 0 | this.slaveOk = options == null || options.slaveOk == null ? db.slaveOk : options.slaveOk; |
| 49 | 0 | this.serializeFunctions = options == null || options.serializeFunctions == null ? db.serializeFunctions : options.serializeFunctions; |
| 50 | 0 | this.raw = options == null || options.raw == null ? db.raw : options.raw; |
| 51 | ||
| 52 | 0 | this.readPreference = options == null || options.readPreference == null ? db.serverConfig.options.readPreference : options.readPreference; |
| 53 | 0 | this.readPreference = this.readPreference == null ? 'primary' : this.readPreference; |
| 54 | ||
| 55 | ||
| 56 | 0 | this.pkFactory = pkFactory == null |
| 57 | ? ObjectID | |
| 58 | : pkFactory; | |
| 59 | ||
| 60 | // Server Capabilities | |
| 61 | 0 | this.serverCapabilities = this.db.serverConfig._serverCapabilities; |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * Inserts a single document or a an array of documents into MongoDB. | |
| 66 | * | |
| 67 | * Options | |
| 68 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 69 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 70 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 71 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 72 | * - **continueOnError/keepGoing** {Boolean, default:false}, keep inserting documents even if one document has an error, *mongodb 1.9.1 >*. | |
| 73 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 74 | * - **forceServerObjectId** {Boolean, default:false}, let server assign ObjectId instead of the driver | |
| 75 | * - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS) | |
| 76 | * | |
| 77 | * Deprecated Options | |
| 78 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 79 | * | |
| 80 | * @param {Array|Object} docs | |
| 81 | * @param {Object} [options] optional options for insert command | |
| 82 | * @param {Function} [callback] optional callback for the function, must be provided when using a writeconcern | |
| 83 | * @return {null} | |
| 84 | * @api public | |
| 85 | */ | |
| 86 | 2 | Collection.prototype.insert = function() { return core.insert; }(); |
| 87 | ||
| 88 | /** | |
| 89 | * Removes documents specified by `selector` from the db. | |
| 90 | * | |
| 91 | * Options | |
| 92 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 93 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 94 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 95 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 96 | * - **single** {Boolean, default:false}, removes the first document found. | |
| 97 | * | |
| 98 | * Deprecated Options | |
| 99 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 100 | * | |
| 101 | * @param {Object} [selector] optional select, no selector is equivalent to removing all documents. | |
| 102 | * @param {Object} [options] additional options during remove. | |
| 103 | * @param {Function} [callback] must be provided if you performing a remove with a writeconcern | |
| 104 | * @return {null} | |
| 105 | * @api public | |
| 106 | */ | |
| 107 | 2 | Collection.prototype.remove = function() { return core.remove; }(); |
| 108 | ||
| 109 | /** | |
| 110 | * Renames the collection. | |
| 111 | * | |
| 112 | * Options | |
| 113 | * - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists. | |
| 114 | * | |
| 115 | * @param {String} newName the new name of the collection. | |
| 116 | * @param {Object} [options] returns option results. | |
| 117 | * @param {Function} callback the callback accepting the result | |
| 118 | * @return {null} | |
| 119 | * @api public | |
| 120 | */ | |
| 121 | 2 | Collection.prototype.rename = function() { return commands.rename; }(); |
| 122 | ||
| 123 | /** | |
| 124 | * Save a document. Simple full document replacement function. Not recommended for efficiency, use atomic | |
| 125 | * operators and update instead for more efficient operations. | |
| 126 | * | |
| 127 | * Options | |
| 128 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 129 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 130 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 131 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 132 | * | |
| 133 | * Deprecated Options | |
| 134 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 135 | * | |
| 136 | * @param {Object} [doc] the document to save | |
| 137 | * @param {Object} [options] additional options during remove. | |
| 138 | * @param {Function} [callback] must be provided if you performing a safe save | |
| 139 | * @return {null} | |
| 140 | * @api public | |
| 141 | */ | |
| 142 | 2 | Collection.prototype.save = function() { return core.save; }(); |
| 143 | ||
| 144 | /** | |
| 145 | * Updates documents. | |
| 146 | * | |
| 147 | * Options | |
| 148 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 149 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 150 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 151 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 152 | * - **upsert** {Boolean, default:false}, perform an upsert operation. | |
| 153 | * - **multi** {Boolean, default:false}, update all documents matching the selector. | |
| 154 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 155 | * - **checkKeys** {Boolean, default:true}, allows for disabling of document key checking (WARNING OPENS YOU UP TO INJECTION ATTACKS) | |
| 156 | * | |
| 157 | * Deprecated Options | |
| 158 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 159 | * | |
| 160 | * @param {Object} selector the query to select the document/documents to be updated | |
| 161 | * @param {Object} document the fields/vals to be updated, or in the case of an upsert operation, inserted. | |
| 162 | * @param {Object} [options] additional options during update. | |
| 163 | * @param {Function} [callback] must be provided if you performing an update with a writeconcern | |
| 164 | * @return {null} | |
| 165 | * @api public | |
| 166 | */ | |
| 167 | 2 | Collection.prototype.update = function() { return core.update; }(); |
| 168 | ||
| 169 | /** | |
| 170 | * The distinct command returns returns a list of distinct values for the given key across a collection. | |
| 171 | * | |
| 172 | * Options | |
| 173 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 174 | * | |
| 175 | * @param {String} key key to run distinct against. | |
| 176 | * @param {Object} [query] option query to narrow the returned objects. | |
| 177 | * @param {Object} [options] additional options during update. | |
| 178 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from distinct or null if an error occured. | |
| 179 | * @return {null} | |
| 180 | * @api public | |
| 181 | */ | |
| 182 | 2 | Collection.prototype.distinct = function() { return commands.distinct; }(); |
| 183 | ||
| 184 | /** | |
| 185 | * Count number of matching documents in the db to a query. | |
| 186 | * | |
| 187 | * Options | |
| 188 | * - **skip** {Number}, The number of documents to skip for the count. | |
| 189 | * - **limit** {Number}, The limit of documents to count. | |
| 190 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 191 | * | |
| 192 | * @param {Object} [query] query to filter by before performing count. | |
| 193 | * @param {Object} [options] additional options during count. | |
| 194 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the count method or null if an error occured. | |
| 195 | * @return {null} | |
| 196 | * @api public | |
| 197 | */ | |
| 198 | 2 | Collection.prototype.count = function() { return commands.count; }(); |
| 199 | ||
| 200 | /** | |
| 201 | * Drop the collection | |
| 202 | * | |
| 203 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the drop method or null if an error occured. | |
| 204 | * @return {null} | |
| 205 | * @api public | |
| 206 | */ | |
| 207 | 1 | Collection.prototype.drop = function drop(callback) { |
| 208 | 0 | this.db.dropCollection(this.collectionName, callback); |
| 209 | }; | |
| 210 | ||
| 211 | /** | |
| 212 | * Find and update a document. | |
| 213 | * | |
| 214 | * Options | |
| 215 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 216 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 217 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 218 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 219 | * - **remove** {Boolean, default:false}, set to true to remove the object before returning. | |
| 220 | * - **upsert** {Boolean, default:false}, perform an upsert operation. | |
| 221 | * - **new** {Boolean, default:false}, set to true if you want to return the modified object rather than the original. Ignored for remove. | |
| 222 | * | |
| 223 | * Deprecated Options | |
| 224 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 225 | * | |
| 226 | * @param {Object} query query object to locate the object to modify | |
| 227 | * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate | |
| 228 | * @param {Object} doc - the fields/vals to be updated | |
| 229 | * @param {Object} [options] additional options during update. | |
| 230 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndModify method or null if an error occured. | |
| 231 | * @return {null} | |
| 232 | * @api public | |
| 233 | */ | |
| 234 | 2 | Collection.prototype.findAndModify = function() { return core.findAndModify; }(); |
| 235 | ||
| 236 | /** | |
| 237 | * Find and remove a document | |
| 238 | * | |
| 239 | * Options | |
| 240 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 241 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 242 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 243 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 244 | * | |
| 245 | * Deprecated Options | |
| 246 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 247 | * | |
| 248 | * @param {Object} query query object to locate the object to modify | |
| 249 | * @param {Array} sort - if multiple docs match, choose the first one in the specified sort order as the object to manipulate | |
| 250 | * @param {Object} [options] additional options during update. | |
| 251 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findAndRemove method or null if an error occured. | |
| 252 | * @return {null} | |
| 253 | * @api public | |
| 254 | */ | |
| 255 | 2 | Collection.prototype.findAndRemove = function() { return core.findAndRemove; }(); |
| 256 | ||
| 257 | /** | |
| 258 | * Creates a cursor for a query that can be used to iterate over results from MongoDB | |
| 259 | * | |
| 260 | * Various argument possibilities | |
| 261 | * - callback? | |
| 262 | * - selector, callback?, | |
| 263 | * - selector, fields, callback? | |
| 264 | * - selector, options, callback? | |
| 265 | * - selector, fields, options, callback? | |
| 266 | * - selector, fields, skip, limit, callback? | |
| 267 | * - selector, fields, skip, limit, timeout, callback? | |
| 268 | * | |
| 269 | * Options | |
| 270 | * - **limit** {Number, default:0}, sets the limit of documents returned in the query. | |
| 271 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 272 | * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} | |
| 273 | * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). | |
| 274 | * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} | |
| 275 | * - **explain** {Boolean, default:false}, explain the query instead of returning the data. | |
| 276 | * - **snapshot** {Boolean, default:false}, snapshot query. | |
| 277 | * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. | |
| 278 | * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. | |
| 279 | * - **tailableRetryInterval** {Number, default:100}, specify the miliseconds between getMores on tailable cursor. | |
| 280 | * - **numberOfRetries** {Number, default:5}, specify the number of times to retry the tailable cursor. | |
| 281 | * - **awaitdata** {Boolean, default:false} allow the cursor to wait for data, only applicable for tailable cursor. | |
| 282 | * - **oplogReplay** {Boolean, default:false} sets an internal flag, only applicable for tailable cursor. | |
| 283 | * - **exhaust** {Boolean, default:false} have the server send all the documents at once as getMore packets, not recommended. | |
| 284 | * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. | |
| 285 | * - **returnKey** {Boolean, default:false}, only return the index key. | |
| 286 | * - **maxScan** {Number}, Limit the number of items to scan. | |
| 287 | * - **min** {Number}, Set index bounds. | |
| 288 | * - **max** {Number}, Set index bounds. | |
| 289 | * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. | |
| 290 | * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 291 | * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. | |
| 292 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 293 | * - **numberOfRetries** {Number, default:5}, if using awaidata specifies the number of times to retry on timeout. | |
| 294 | * - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system | |
| 295 | * | |
| 296 | * @param {Object|ObjectID} query query object to locate the object to modify | |
| 297 | * @param {Object} [options] additional options during update. | |
| 298 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the find method or null if an error occured. | |
| 299 | * @return {Cursor} returns a cursor to the query | |
| 300 | * @api public | |
| 301 | */ | |
| 302 | 2 | Collection.prototype.find = function() { return query.find; }(); |
| 303 | ||
| 304 | /** | |
| 305 | * Finds a single document based on the query | |
| 306 | * | |
| 307 | * Various argument possibilities | |
| 308 | * - callback? | |
| 309 | * - selector, callback?, | |
| 310 | * - selector, fields, callback? | |
| 311 | * - selector, options, callback? | |
| 312 | * - selector, fields, options, callback? | |
| 313 | * - selector, fields, skip, limit, callback? | |
| 314 | * - selector, fields, skip, limit, timeout, callback? | |
| 315 | * | |
| 316 | * Options | |
| 317 | * - **limit** {Number, default:0}, sets the limit of documents returned in the query. | |
| 318 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 319 | * - **fields** {Object}, the fields to return in the query. Object of fields to include or exclude (not both), {'a':1} | |
| 320 | * - **skip** {Number, default:0}, set to skip N documents ahead in your query (useful for pagination). | |
| 321 | * - **hint** {Object}, tell the query to use specific indexes in the query. Object of indexes to use, {'_id':1} | |
| 322 | * - **explain** {Boolean, default:false}, explain the query instead of returning the data. | |
| 323 | * - **snapshot** {Boolean, default:false}, snapshot query. | |
| 324 | * - **timeout** {Boolean, default:false}, specify if the cursor can timeout. | |
| 325 | * - **tailable** {Boolean, default:false}, specify if the cursor is tailable. | |
| 326 | * - **batchSize** {Number, default:0}, set the batchSize for the getMoreCommand when iterating over the query results. | |
| 327 | * - **returnKey** {Boolean, default:false}, only return the index key. | |
| 328 | * - **maxScan** {Number}, Limit the number of items to scan. | |
| 329 | * - **min** {Number}, Set index bounds. | |
| 330 | * - **max** {Number}, Set index bounds. | |
| 331 | * - **showDiskLoc** {Boolean, default:false}, Show disk location of results. | |
| 332 | * - **comment** {String}, You can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 333 | * - **raw** {Boolean, default:false}, Return all BSON documents as Raw Buffer documents. | |
| 334 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 335 | * - **partial** {Boolean, default:false}, specify if the cursor should return partial results when querying against a sharded system | |
| 336 | * | |
| 337 | * @param {Object|ObjectID} query query object to locate the object to modify | |
| 338 | * @param {Object} [options] additional options during update. | |
| 339 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the findOne method or null if an error occured. | |
| 340 | * @return {Cursor} returns a cursor to the query | |
| 341 | * @api public | |
| 342 | */ | |
| 343 | 2 | Collection.prototype.findOne = function() { return query.findOne; }(); |
| 344 | ||
| 345 | /** | |
| 346 | * Creates an index on the collection. | |
| 347 | * | |
| 348 | * Options | |
| 349 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 350 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 351 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 352 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 353 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 354 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 355 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 356 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 357 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 358 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 359 | * - **v** {Number}, specify the format version of the indexes. | |
| 360 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 361 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 362 | * | |
| 363 | * Deprecated Options | |
| 364 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 365 | * | |
| 366 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 367 | * @param {Object} [options] additional options during update. | |
| 368 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the createIndex method or null if an error occured. | |
| 369 | * @return {null} | |
| 370 | * @api public | |
| 371 | */ | |
| 372 | 2 | Collection.prototype.createIndex = function() { return index.createIndex; }(); |
| 373 | ||
| 374 | /** | |
| 375 | * Ensures that an index exists, if it does not it creates it | |
| 376 | * | |
| 377 | * Options | |
| 378 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 379 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 380 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 381 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 382 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 383 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 384 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 385 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 386 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 387 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 388 | * - **v** {Number}, specify the format version of the indexes. | |
| 389 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 390 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 391 | * | |
| 392 | * Deprecated Options | |
| 393 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 394 | * | |
| 395 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 396 | * @param {Object} [options] additional options during update. | |
| 397 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the ensureIndex method or null if an error occured. | |
| 398 | * @return {null} | |
| 399 | * @api public | |
| 400 | */ | |
| 401 | 2 | Collection.prototype.ensureIndex = function() { return index.ensureIndex; }(); |
| 402 | ||
| 403 | /** | |
| 404 | * Retrieves this collections index info. | |
| 405 | * | |
| 406 | * Options | |
| 407 | * - **full** {Boolean, default:false}, returns the full raw index information. | |
| 408 | * | |
| 409 | * @param {Object} [options] additional options during update. | |
| 410 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexInformation method or null if an error occured. | |
| 411 | * @return {null} | |
| 412 | * @api public | |
| 413 | */ | |
| 414 | 2 | Collection.prototype.indexInformation = function() { return index.indexInformation; }(); |
| 415 | ||
| 416 | /** | |
| 417 | * Drops an index from this collection. | |
| 418 | * | |
| 419 | * @param {String} name | |
| 420 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndex method or null if an error occured. | |
| 421 | * @return {null} | |
| 422 | * @api public | |
| 423 | */ | |
| 424 | 1 | Collection.prototype.dropIndex = function dropIndex (name, callback) { |
| 425 | 0 | this.db.dropIndex(this.collectionName, name, callback); |
| 426 | }; | |
| 427 | ||
| 428 | /** | |
| 429 | * Drops all indexes from this collection. | |
| 430 | * | |
| 431 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropAllIndexes method or null if an error occured. | |
| 432 | * @return {null} | |
| 433 | * @api public | |
| 434 | */ | |
| 435 | 2 | Collection.prototype.dropAllIndexes = function() { return index.dropAllIndexes; }(); |
| 436 | ||
| 437 | /** | |
| 438 | * Drops all indexes from this collection. | |
| 439 | * | |
| 440 | * @deprecated | |
| 441 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the dropIndexes method or null if an error occured. | |
| 442 | * @return {null} | |
| 443 | * @api private | |
| 444 | */ | |
| 445 | 2 | Collection.prototype.dropIndexes = function() { return Collection.prototype.dropAllIndexes; }(); |
| 446 | ||
| 447 | /** | |
| 448 | * Reindex all indexes on the collection | |
| 449 | * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. | |
| 450 | * | |
| 451 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the reIndex method or null if an error occured. | |
| 452 | * @return {null} | |
| 453 | * @api public | |
| 454 | **/ | |
| 455 | 1 | Collection.prototype.reIndex = function(callback) { |
| 456 | 0 | this.db.reIndex(this.collectionName, callback); |
| 457 | } | |
| 458 | ||
| 459 | /** | |
| 460 | * Run Map Reduce across a collection. Be aware that the inline option for out will return an array of results not a collection. | |
| 461 | * | |
| 462 | * Options | |
| 463 | * - **out** {Object}, sets the output target for the map reduce job. *{inline:1} | {replace:'collectionName'} | {merge:'collectionName'} | {reduce:'collectionName'}* | |
| 464 | * - **query** {Object}, query filter object. | |
| 465 | * - **sort** {Object}, sorts the input objects using this key. Useful for optimization, like sorting by the emit key for fewer reduces. | |
| 466 | * - **limit** {Number}, number of objects to return from collection. | |
| 467 | * - **keeptemp** {Boolean, default:false}, keep temporary data. | |
| 468 | * - **finalize** {Function | String}, finalize function. | |
| 469 | * - **scope** {Object}, can pass in variables that can be access from map/reduce/finalize. | |
| 470 | * - **jsMode** {Boolean, default:false}, it is possible to make the execution stay in JS. Provided in MongoDB > 2.0.X. | |
| 471 | * - **verbose** {Boolean, default:false}, provide statistics on job execution time. | |
| 472 | * - **readPreference** {String, only for inline results}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 473 | * | |
| 474 | * @param {Function|String} map the mapping function. | |
| 475 | * @param {Function|String} reduce the reduce function. | |
| 476 | * @param {Objects} [options] options for the map reduce job. | |
| 477 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the mapReduce method or null if an error occured. | |
| 478 | * @return {null} | |
| 479 | * @api public | |
| 480 | */ | |
| 481 | 2 | Collection.prototype.mapReduce = function() { return aggregation.mapReduce; }(); |
| 482 | ||
| 483 | /** | |
| 484 | * Run a group command across a collection | |
| 485 | * | |
| 486 | * Options | |
| 487 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 488 | * | |
| 489 | * @param {Object|Array|Function|Code} keys an object, array or function expressing the keys to group by. | |
| 490 | * @param {Object} condition an optional condition that must be true for a row to be considered. | |
| 491 | * @param {Object} initial initial value of the aggregation counter object. | |
| 492 | * @param {Function|Code} reduce the reduce function aggregates (reduces) the objects iterated | |
| 493 | * @param {Function|Code} finalize an optional function to be run on each item in the result set just before the item is returned. | |
| 494 | * @param {Boolean} command specify if you wish to run using the internal group command or using eval, default is true. | |
| 495 | * @param {Object} [options] additional options during update. | |
| 496 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the group method or null if an error occured. | |
| 497 | * @return {null} | |
| 498 | * @api public | |
| 499 | */ | |
| 500 | 2 | Collection.prototype.group = function() { return aggregation.group; }(); |
| 501 | ||
| 502 | /** | |
| 503 | * Returns the options of the collection. | |
| 504 | * | |
| 505 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the options method or null if an error occured. | |
| 506 | * @return {null} | |
| 507 | * @api public | |
| 508 | */ | |
| 509 | 2 | Collection.prototype.options = function() { return commands.options; }(); |
| 510 | ||
| 511 | /** | |
| 512 | * Returns if the collection is a capped collection | |
| 513 | * | |
| 514 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the isCapped method or null if an error occured. | |
| 515 | * @return {null} | |
| 516 | * @api public | |
| 517 | */ | |
| 518 | 2 | Collection.prototype.isCapped = function() { return commands.isCapped; }(); |
| 519 | ||
| 520 | /** | |
| 521 | * Checks if one or more indexes exist on the collection | |
| 522 | * | |
| 523 | * @param {String|Array} indexNames check if one or more indexes exist on the collection. | |
| 524 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexExists method or null if an error occured. | |
| 525 | * @return {null} | |
| 526 | * @api public | |
| 527 | */ | |
| 528 | 2 | Collection.prototype.indexExists = function() { return index.indexExists; }(); |
| 529 | ||
| 530 | /** | |
| 531 | * Execute the geoNear command to search for items in the collection | |
| 532 | * | |
| 533 | * Options | |
| 534 | * - **num** {Number}, max number of results to return. | |
| 535 | * - **maxDistance** {Number}, include results up to maxDistance from the point. | |
| 536 | * - **distanceMultiplier** {Number}, include a value to multiply the distances with allowing for range conversions. | |
| 537 | * - **query** {Object}, filter the results by a query. | |
| 538 | * - **spherical** {Boolean, default:false}, perform query using a spherical model. | |
| 539 | * - **uniqueDocs** {Boolean, default:false}, the closest location in a document to the center of the search region will always be returned MongoDB > 2.X. | |
| 540 | * - **includeLocs** {Boolean, default:false}, include the location data fields in the top level of the results MongoDB > 2.X. | |
| 541 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 542 | * | |
| 543 | * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. | |
| 544 | * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. | |
| 545 | * @param {Objects} [options] options for the map reduce job. | |
| 546 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoNear method or null if an error occured. | |
| 547 | * @return {null} | |
| 548 | * @api public | |
| 549 | */ | |
| 550 | 2 | Collection.prototype.geoNear = function() { return geo.geoNear; }(); |
| 551 | ||
| 552 | /** | |
| 553 | * Execute a geo search using a geo haystack index on a collection. | |
| 554 | * | |
| 555 | * Options | |
| 556 | * - **maxDistance** {Number}, include results up to maxDistance from the point. | |
| 557 | * - **search** {Object}, filter the results by a query. | |
| 558 | * - **limit** {Number}, max number of results to return. | |
| 559 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 560 | * | |
| 561 | * @param {Number} x point to search on the x axis, ensure the indexes are ordered in the same order. | |
| 562 | * @param {Number} y point to search on the y axis, ensure the indexes are ordered in the same order. | |
| 563 | * @param {Objects} [options] options for the map reduce job. | |
| 564 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the geoHaystackSearch method or null if an error occured. | |
| 565 | * @return {null} | |
| 566 | * @api public | |
| 567 | */ | |
| 568 | 2 | Collection.prototype.geoHaystackSearch = function() { return geo.geoHaystackSearch; }(); |
| 569 | ||
| 570 | /** | |
| 571 | * Retrieve all the indexes on the collection. | |
| 572 | * | |
| 573 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the indexes method or null if an error occured. | |
| 574 | * @return {null} | |
| 575 | * @api public | |
| 576 | */ | |
| 577 | 1 | Collection.prototype.indexes = function indexes(callback) { |
| 578 | 0 | this.db.indexInformation(this.collectionName, {full:true}, callback); |
| 579 | } | |
| 580 | ||
| 581 | /** | |
| 582 | * Execute an aggregation framework pipeline against the collection, needs MongoDB >= 2.2 | |
| 583 | * | |
| 584 | * Options | |
| 585 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 586 | * | |
| 587 | * @param {Array} array containing all the aggregation framework commands for the execution. | |
| 588 | * @param {Object} [options] additional options during update. | |
| 589 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the aggregate method or null if an error occured. | |
| 590 | * @return {null} | |
| 591 | * @api public | |
| 592 | */ | |
| 593 | 2 | Collection.prototype.aggregate = function() { return aggregation.aggregate; }(); |
| 594 | ||
| 595 | /** | |
| 596 | * Get all the collection statistics. | |
| 597 | * | |
| 598 | * Options | |
| 599 | * - **scale** {Number}, divide the returned sizes by scale value. | |
| 600 | * - **readPreference** {String}, the preferred read preference, require('mongodb').ReadPreference ((ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 601 | * | |
| 602 | * @param {Objects} [options] options for the stats command. | |
| 603 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the stats method or null if an error occured. | |
| 604 | * @return {null} | |
| 605 | * @api public | |
| 606 | */ | |
| 607 | 2 | Collection.prototype.stats = function() { return commands.stats; }(); |
| 608 | ||
| 609 | /** | |
| 610 | * @ignore | |
| 611 | */ | |
| 612 | 1 | Object.defineProperty(Collection.prototype, "hint", { |
| 613 | enumerable: true | |
| 614 | , get: function () { | |
| 615 | 0 | return this.internalHint; |
| 616 | } | |
| 617 | , set: function (v) { | |
| 618 | 0 | this.internalHint = shared.normalizeHintField(v); |
| 619 | } | |
| 620 | }); | |
| 621 | ||
| 622 | /** | |
| 623 | * Expose. | |
| 624 | */ | |
| 625 | 1 | exports.Collection = Collection; |
| 626 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils') | |
| 3 | , AggregationCursor = require('../aggregation_cursor').AggregationCursor | |
| 4 | , Code = require('bson').Code | |
| 5 | , DbCommand = require('../commands/db_command').DbCommand; | |
| 6 | ||
| 7 | /** | |
| 8 | * Functions that are passed as scope args must | |
| 9 | * be converted to Code instances. | |
| 10 | * @ignore | |
| 11 | */ | |
| 12 | 1 | function processScope (scope) { |
| 13 | 0 | if (!utils.isObject(scope)) { |
| 14 | 0 | return scope; |
| 15 | } | |
| 16 | ||
| 17 | 0 | var keys = Object.keys(scope); |
| 18 | 0 | var i = keys.length; |
| 19 | 0 | var key; |
| 20 | ||
| 21 | 0 | while (i--) { |
| 22 | 0 | key = keys[i]; |
| 23 | 0 | if ('function' == typeof scope[key]) { |
| 24 | 0 | scope[key] = new Code(String(scope[key])); |
| 25 | } else { | |
| 26 | 0 | scope[key] = processScope(scope[key]); |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | 0 | return scope; |
| 31 | } | |
| 32 | ||
| 33 | 1 | var pipe = function() { |
| 34 | 0 | return new AggregationCursor(this, this.serverCapabilities); |
| 35 | } | |
| 36 | ||
| 37 | 1 | var mapReduce = function mapReduce (map, reduce, options, callback) { |
| 38 | 0 | if ('function' === typeof options) callback = options, options = {}; |
| 39 | // Out must allways be defined (make sure we don't break weirdly on pre 1.8+ servers) | |
| 40 | 0 | if(null == options.out) { |
| 41 | 0 | throw new Error("the out option parameter must be defined, see mongodb docs for possible values"); |
| 42 | } | |
| 43 | ||
| 44 | 0 | if ('function' === typeof map) { |
| 45 | 0 | map = map.toString(); |
| 46 | } | |
| 47 | ||
| 48 | 0 | if ('function' === typeof reduce) { |
| 49 | 0 | reduce = reduce.toString(); |
| 50 | } | |
| 51 | ||
| 52 | 0 | if ('function' === typeof options.finalize) { |
| 53 | 0 | options.finalize = options.finalize.toString(); |
| 54 | } | |
| 55 | ||
| 56 | 0 | var mapCommandHash = { |
| 57 | mapreduce: this.collectionName | |
| 58 | , map: map | |
| 59 | , reduce: reduce | |
| 60 | }; | |
| 61 | ||
| 62 | // Add any other options passed in | |
| 63 | 0 | for (var name in options) { |
| 64 | 0 | if ('scope' == name) { |
| 65 | 0 | mapCommandHash[name] = processScope(options[name]); |
| 66 | } else { | |
| 67 | 0 | mapCommandHash[name] = options[name]; |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | // Set read preference if we set one | |
| 72 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 73 | ||
| 74 | // If we have a read preference and inline is not set as output fail hard | |
| 75 | 0 | if((readPreference != false && readPreference != 'primary') |
| 76 | && options['out'] && (options['out'].inline != 1 && options['out'] != 'inline')) { | |
| 77 | 0 | readPreference = 'primary'; |
| 78 | } | |
| 79 | ||
| 80 | // self | |
| 81 | 0 | var self = this; |
| 82 | 0 | var cmd = DbCommand.createDbCommand(this.db, mapCommandHash); |
| 83 | ||
| 84 | 0 | this.db._executeQueryCommand(cmd, {read:readPreference}, function (err, result) { |
| 85 | 0 | if(err) return callback(err); |
| 86 | 0 | if(!result || !result.documents || result.documents.length == 0) |
| 87 | 0 | return callback(Error("command failed to return results"), null) |
| 88 | ||
| 89 | // Check if we have an error | |
| 90 | 0 | if(1 != result.documents[0].ok || result.documents[0].err || result.documents[0].errmsg) { |
| 91 | 0 | return callback(utils.toError(result.documents[0])); |
| 92 | } | |
| 93 | ||
| 94 | // Create statistics value | |
| 95 | 0 | var stats = {}; |
| 96 | 0 | if(result.documents[0].timeMillis) stats['processtime'] = result.documents[0].timeMillis; |
| 97 | 0 | if(result.documents[0].counts) stats['counts'] = result.documents[0].counts; |
| 98 | 0 | if(result.documents[0].timing) stats['timing'] = result.documents[0].timing; |
| 99 | ||
| 100 | // invoked with inline? | |
| 101 | 0 | if(result.documents[0].results) { |
| 102 | // If we wish for no verbosity | |
| 103 | 0 | if(options['verbose'] == null || !options['verbose']) { |
| 104 | 0 | return callback(null, result.documents[0].results); |
| 105 | } | |
| 106 | 0 | return callback(null, result.documents[0].results, stats); |
| 107 | } | |
| 108 | ||
| 109 | // The returned collection | |
| 110 | 0 | var collection = null; |
| 111 | ||
| 112 | // If we have an object it's a different db | |
| 113 | 0 | if(result.documents[0].result != null && typeof result.documents[0].result == 'object') { |
| 114 | 0 | var doc = result.documents[0].result; |
| 115 | 0 | collection = self.db.db(doc.db).collection(doc.collection); |
| 116 | } else { | |
| 117 | // Create a collection object that wraps the result collection | |
| 118 | 0 | collection = self.db.collection(result.documents[0].result) |
| 119 | } | |
| 120 | ||
| 121 | // If we wish for no verbosity | |
| 122 | 0 | if(options['verbose'] == null || !options['verbose']) { |
| 123 | 0 | return callback(err, collection); |
| 124 | } | |
| 125 | ||
| 126 | // Return stats as third set of values | |
| 127 | 0 | callback(err, collection, stats); |
| 128 | }); | |
| 129 | }; | |
| 130 | ||
| 131 | /** | |
| 132 | * Group function helper | |
| 133 | * @ignore | |
| 134 | */ | |
| 135 | 1 | var groupFunction = function () { |
| 136 | 0 | var c = db[ns].find(condition); |
| 137 | 0 | var map = new Map(); |
| 138 | 0 | var reduce_function = reduce; |
| 139 | ||
| 140 | 0 | while (c.hasNext()) { |
| 141 | 0 | var obj = c.next(); |
| 142 | 0 | var key = {}; |
| 143 | ||
| 144 | 0 | for (var i = 0, len = keys.length; i < len; ++i) { |
| 145 | 0 | var k = keys[i]; |
| 146 | 0 | key[k] = obj[k]; |
| 147 | } | |
| 148 | ||
| 149 | 0 | var aggObj = map.get(key); |
| 150 | ||
| 151 | 0 | if (aggObj == null) { |
| 152 | 0 | var newObj = Object.extend({}, key); |
| 153 | 0 | aggObj = Object.extend(newObj, initial); |
| 154 | 0 | map.put(key, aggObj); |
| 155 | } | |
| 156 | ||
| 157 | 0 | reduce_function(obj, aggObj); |
| 158 | } | |
| 159 | ||
| 160 | 0 | return { "result": map.values() }; |
| 161 | }.toString(); | |
| 162 | ||
| 163 | 1 | var group = function group(keys, condition, initial, reduce, finalize, command, options, callback) { |
| 164 | 0 | var args = Array.prototype.slice.call(arguments, 3); |
| 165 | 0 | callback = args.pop(); |
| 166 | // Fetch all commands | |
| 167 | 0 | reduce = args.length ? args.shift() : null; |
| 168 | 0 | finalize = args.length ? args.shift() : null; |
| 169 | 0 | command = args.length ? args.shift() : null; |
| 170 | 0 | options = args.length ? args.shift() || {} : {}; |
| 171 | ||
| 172 | // Make sure we are backward compatible | |
| 173 | 0 | if(!(typeof finalize == 'function')) { |
| 174 | 0 | command = finalize; |
| 175 | 0 | finalize = null; |
| 176 | } | |
| 177 | ||
| 178 | 0 | if (!Array.isArray(keys) && keys instanceof Object && typeof(keys) !== 'function' && !(keys instanceof Code)) { |
| 179 | 0 | keys = Object.keys(keys); |
| 180 | } | |
| 181 | ||
| 182 | 0 | if(typeof reduce === 'function') { |
| 183 | 0 | reduce = reduce.toString(); |
| 184 | } | |
| 185 | ||
| 186 | 0 | if(typeof finalize === 'function') { |
| 187 | 0 | finalize = finalize.toString(); |
| 188 | } | |
| 189 | ||
| 190 | // Set up the command as default | |
| 191 | 0 | command = command == null ? true : command; |
| 192 | ||
| 193 | // Execute using the command | |
| 194 | 0 | if(command) { |
| 195 | 0 | var reduceFunction = reduce instanceof Code |
| 196 | ? reduce | |
| 197 | : new Code(reduce); | |
| 198 | ||
| 199 | 0 | var selector = { |
| 200 | group: { | |
| 201 | 'ns': this.collectionName | |
| 202 | , '$reduce': reduceFunction | |
| 203 | , 'cond': condition | |
| 204 | , 'initial': initial | |
| 205 | , 'out': "inline" | |
| 206 | } | |
| 207 | }; | |
| 208 | ||
| 209 | // if finalize is defined | |
| 210 | 0 | if(finalize != null) selector.group['finalize'] = finalize; |
| 211 | // Set up group selector | |
| 212 | 0 | if ('function' === typeof keys || keys instanceof Code) { |
| 213 | 0 | selector.group.$keyf = keys instanceof Code |
| 214 | ? keys | |
| 215 | : new Code(keys); | |
| 216 | } else { | |
| 217 | 0 | var hash = {}; |
| 218 | 0 | keys.forEach(function (key) { |
| 219 | 0 | hash[key] = 1; |
| 220 | }); | |
| 221 | 0 | selector.group.key = hash; |
| 222 | } | |
| 223 | ||
| 224 | 0 | var cmd = DbCommand.createDbSlaveOkCommand(this.db, selector); |
| 225 | // Set read preference if we set one | |
| 226 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 227 | // Execute the command | |
| 228 | 0 | this.db._executeQueryCommand(cmd |
| 229 | , {read:readPreference} | |
| 230 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 231 | 0 | if(err) return callback(err, null); |
| 232 | 0 | callback(null, result.retval); |
| 233 | })); | |
| 234 | } else { | |
| 235 | // Create execution scope | |
| 236 | 0 | var scope = reduce != null && reduce instanceof Code |
| 237 | ? reduce.scope | |
| 238 | : {}; | |
| 239 | ||
| 240 | 0 | scope.ns = this.collectionName; |
| 241 | 0 | scope.keys = keys; |
| 242 | 0 | scope.condition = condition; |
| 243 | 0 | scope.initial = initial; |
| 244 | ||
| 245 | // Pass in the function text to execute within mongodb. | |
| 246 | 0 | var groupfn = groupFunction.replace(/ reduce;/, reduce.toString() + ';'); |
| 247 | ||
| 248 | 0 | this.db.eval(new Code(groupfn, scope), function (err, results) { |
| 249 | 0 | if (err) return callback(err, null); |
| 250 | 0 | callback(null, results.result || results); |
| 251 | }); | |
| 252 | } | |
| 253 | }; | |
| 254 | ||
| 255 | 1 | var aggregate = function(pipeline, options, callback) { |
| 256 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 257 | 0 | callback = args.pop(); |
| 258 | 0 | var self = this; |
| 259 | ||
| 260 | // If we have any of the supported options in the options object | |
| 261 | 0 | var opts = args[args.length - 1]; |
| 262 | 0 | options = opts.readPreference |
| 263 | || opts.explain | |
| 264 | || opts.cursor | |
| 265 | || opts.out | |
| 266 | || opts.allowDiskUsage ? args.pop() : {} | |
| 267 | // If the callback is the option (as for cursor override it) | |
| 268 | 0 | if(typeof callback == 'object' && callback != null) options = callback; |
| 269 | ||
| 270 | // Convert operations to an array | |
| 271 | 0 | if(!Array.isArray(args[0])) { |
| 272 | 0 | pipeline = []; |
| 273 | // Push all the operations to the pipeline | |
| 274 | 0 | for(var i = 0; i < args.length; i++) pipeline.push(args[i]); |
| 275 | } | |
| 276 | ||
| 277 | // Is the user requesting a cursor | |
| 278 | 0 | if(options.cursor != null && options.out == null) { |
| 279 | // Set the aggregation cursor options | |
| 280 | 0 | var agg_cursor_options = options.cursor; |
| 281 | 0 | agg_cursor_options.pipe = pipeline; |
| 282 | 0 | agg_cursor_options.allowDiskUsage = options.allowDiskUsage == null ? false : options.allowDiskUsage; |
| 283 | // Return the aggregation cursor | |
| 284 | 0 | return new AggregationCursor(this, this.serverCapabilities, agg_cursor_options); |
| 285 | } | |
| 286 | ||
| 287 | // If out was specified | |
| 288 | 0 | if(typeof options.out == 'string') { |
| 289 | 0 | pipeline.push({$out: options.out}); |
| 290 | } | |
| 291 | ||
| 292 | // Build the command | |
| 293 | 0 | var command = { aggregate : this.collectionName, pipeline : pipeline}; |
| 294 | // If we have allowDiskUsage defined | |
| 295 | 0 | if(options.allowDiskUsage) command.allowDiskUsage = options.allowDiskUsage; |
| 296 | ||
| 297 | // Ensure we have the right read preference inheritance | |
| 298 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 299 | // If explain has been specified add it | |
| 300 | 0 | if(options.explain) command.explain = options.explain; |
| 301 | // Execute the command | |
| 302 | 0 | this.db.command(command, options, function(err, result) { |
| 303 | 0 | if(err) { |
| 304 | 0 | callback(err); |
| 305 | 0 | } else if(result['err'] || result['errmsg']) { |
| 306 | 0 | callback(utils.toError(result)); |
| 307 | 0 | } else if(typeof result == 'object' && result['serverPipeline']) { |
| 308 | 0 | callback(null, result['serverPipeline']); |
| 309 | 0 | } else if(typeof result == 'object' && result['stages']) { |
| 310 | 0 | callback(null, result['stages']); |
| 311 | } else { | |
| 312 | 0 | callback(null, result.result); |
| 313 | } | |
| 314 | }); | |
| 315 | } | |
| 316 | ||
| 317 | 1 | exports.mapReduce = mapReduce; |
| 318 | 1 | exports.group = group; |
| 319 | 1 | exports.aggregate = aggregate; |
| 320 | 1 | exports.pipe = pipe; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils') | |
| 3 | , DbCommand = require('../commands/db_command').DbCommand; | |
| 4 | ||
| 5 | 1 | var stats = function stats(options, callback) { |
| 6 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 7 | 0 | callback = args.pop(); |
| 8 | // Fetch all commands | |
| 9 | 0 | options = args.length ? args.shift() || {} : {}; |
| 10 | ||
| 11 | // Build command object | |
| 12 | 0 | var commandObject = { |
| 13 | collStats:this.collectionName, | |
| 14 | } | |
| 15 | ||
| 16 | // Check if we have the scale value | |
| 17 | 0 | if(options['scale'] != null) commandObject['scale'] = options['scale']; |
| 18 | ||
| 19 | // Ensure we have the right read preference inheritance | |
| 20 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 21 | ||
| 22 | // Execute the command | |
| 23 | 0 | this.db.command(commandObject, options, callback); |
| 24 | } | |
| 25 | ||
| 26 | 1 | var count = function count(query, options, callback) { |
| 27 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 28 | 0 | callback = args.pop(); |
| 29 | 0 | query = args.length ? args.shift() || {} : {}; |
| 30 | 0 | options = args.length ? args.shift() || {} : {}; |
| 31 | 0 | var skip = options.skip; |
| 32 | 0 | var limit = options.limit; |
| 33 | 0 | var maxTimeMS = options.maxTimeMS; |
| 34 | ||
| 35 | // Final query | |
| 36 | 0 | var commandObject = { |
| 37 | 'count': this.collectionName | |
| 38 | , 'query': query | |
| 39 | , 'fields': null | |
| 40 | }; | |
| 41 | ||
| 42 | // Add limit and skip if defined | |
| 43 | 0 | if(typeof skip == 'number') commandObject.skip = skip; |
| 44 | 0 | if(typeof limit == 'number') commandObject.limit = limit; |
| 45 | 0 | if(typeof maxTimeMS == 'number') commandObject['$maxTimeMS'] = maxTimeMS; |
| 46 | ||
| 47 | // Set read preference if we set one | |
| 48 | 0 | var readPreference = shared._getReadConcern(this, options); |
| 49 | // Execute the command | |
| 50 | 0 | this.db._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this.db, commandObject) |
| 51 | , {read: readPreference} | |
| 52 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 53 | 0 | if(err) return callback(err, null); |
| 54 | 0 | if(result == null) return callback(new Error("no result returned for count"), null); |
| 55 | 0 | callback(null, result.n); |
| 56 | })); | |
| 57 | }; | |
| 58 | ||
| 59 | 1 | var distinct = function distinct(key, query, options, callback) { |
| 60 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 61 | 0 | callback = args.pop(); |
| 62 | 0 | query = args.length ? args.shift() || {} : {}; |
| 63 | 0 | options = args.length ? args.shift() || {} : {}; |
| 64 | ||
| 65 | 0 | var mapCommandHash = { |
| 66 | 'distinct': this.collectionName | |
| 67 | , 'query': query | |
| 68 | , 'key': key | |
| 69 | }; | |
| 70 | ||
| 71 | // Set read preference if we set one | |
| 72 | 0 | var readPreference = options['readPreference'] ? options['readPreference'] : false; |
| 73 | // Create the command | |
| 74 | 0 | var cmd = DbCommand.createDbSlaveOkCommand(this.db, mapCommandHash); |
| 75 | ||
| 76 | 0 | this.db._executeQueryCommand(cmd, {read:readPreference}, function (err, result) { |
| 77 | 0 | if(err) |
| 78 | 0 | return callback(err); |
| 79 | 0 | if(result.documents[0].ok != 1) |
| 80 | 0 | return callback(new Error(result.documents[0].errmsg)); |
| 81 | 0 | callback(null, result.documents[0].values); |
| 82 | }); | |
| 83 | }; | |
| 84 | ||
| 85 | 1 | var rename = function rename (newName, options, callback) { |
| 86 | 0 | var self = this; |
| 87 | 0 | if(typeof options == 'function') { |
| 88 | 0 | callback = options; |
| 89 | 0 | options = {} |
| 90 | } | |
| 91 | ||
| 92 | // Get collection class | |
| 93 | 0 | var Collection = require('../collection').Collection; |
| 94 | // Ensure the new name is valid | |
| 95 | 0 | shared.checkCollectionName(newName); |
| 96 | ||
| 97 | // Execute the command, return the new renamed collection if successful | |
| 98 | 0 | self.db._executeQueryCommand(DbCommand.createRenameCollectionCommand(self.db, self.collectionName, newName, options) |
| 99 | , utils.handleSingleCommandResultReturn(true, false, function(err, result) { | |
| 100 | 0 | if(err) return callback(err, null) |
| 101 | 0 | try { |
| 102 | 0 | if(options.new_collection) |
| 103 | 0 | return callback(null, new Collection(self.db, newName, self.db.pkFactory)); |
| 104 | 0 | self.collectionName = newName; |
| 105 | 0 | callback(null, self); |
| 106 | } catch(err) { | |
| 107 | 0 | callback(err, null); |
| 108 | } | |
| 109 | })); | |
| 110 | }; | |
| 111 | ||
| 112 | 1 | var options = function options(callback) { |
| 113 | 0 | this.db.collectionsInfo(this.collectionName, function (err, cursor) { |
| 114 | 0 | if (err) return callback(err); |
| 115 | 0 | cursor.nextObject(function (err, document) { |
| 116 | 0 | callback(err, document && document.options || null); |
| 117 | }); | |
| 118 | }); | |
| 119 | }; | |
| 120 | ||
| 121 | 1 | var isCapped = function isCapped(callback) { |
| 122 | 0 | this.options(function(err, document) { |
| 123 | 0 | if(err != null) { |
| 124 | 0 | callback(err); |
| 125 | } else { | |
| 126 | 0 | callback(null, document && document.capped); |
| 127 | } | |
| 128 | }); | |
| 129 | }; | |
| 130 | ||
| 131 | 1 | exports.stats = stats; |
| 132 | 1 | exports.count = count; |
| 133 | 1 | exports.distinct = distinct; |
| 134 | 1 | exports.rename = rename; |
| 135 | 1 | exports.options = options; |
| 136 | 1 | exports.isCapped = isCapped; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var InsertCommand = require('../commands/insert_command').InsertCommand |
| 2 | , DeleteCommand = require('../commands/delete_command').DeleteCommand | |
| 3 | , UpdateCommand = require('../commands/update_command').UpdateCommand | |
| 4 | , DbCommand = require('../commands/db_command').DbCommand | |
| 5 | , utils = require('../utils') | |
| 6 | , hasWriteCommands = require('../utils').hasWriteCommands | |
| 7 | , shared = require('./shared'); | |
| 8 | ||
| 9 | /** | |
| 10 | * Precompiled regexes | |
| 11 | * @ignore | |
| 12 | **/ | |
| 13 | 1 | var eErrorMessages = /No matching object found/; |
| 14 | ||
| 15 | // *************************************************** | |
| 16 | // Insert function | |
| 17 | // *************************************************** | |
| 18 | 1 | var insert = function insert (docs, options, callback) { |
| 19 | 0 | if ('function' === typeof options) callback = options, options = {}; |
| 20 | 0 | if(options == null) options = {}; |
| 21 | 0 | if(!('function' === typeof callback)) callback = null; |
| 22 | ||
| 23 | // Get a connection | |
| 24 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 25 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 26 | // If we support write commands let's perform the insert using it | |
| 27 | 0 | if(!useLegacyOps && hasWriteCommands(connection) |
| 28 | && !Buffer.isBuffer(docs) | |
| 29 | && (!Array.isArray(docs) && docs.length > 0 && !Buffer.isBuffer(docs[0]))) { | |
| 30 | 0 | insertWithWriteCommands(this, Array.isArray(docs) ? docs : [docs], options, callback); |
| 31 | 0 | return this |
| 32 | } | |
| 33 | ||
| 34 | // Backwards compatibility | |
| 35 | 0 | insertAll(this, Array.isArray(docs) ? docs : [docs], options, callback); |
| 36 | 0 | return this; |
| 37 | }; | |
| 38 | ||
| 39 | // | |
| 40 | // Uses the new write commands available from 2.6 > | |
| 41 | // | |
| 42 | 1 | var insertWithWriteCommands = function(self, docs, options, callback) { |
| 43 | // Get the intended namespace for the operation | |
| 44 | 0 | var namespace = self.collectionName; |
| 45 | ||
| 46 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 47 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 48 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 49 | } | |
| 50 | ||
| 51 | // Check if we have passed in continue on error | |
| 52 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 53 | ? options['keepGoing'] : false; | |
| 54 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 55 | ? options['continueOnError'] : continueOnError; | |
| 56 | ||
| 57 | // Do we serialzie functions | |
| 58 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 59 | ? self.serializeFunctions : options.serializeFunctions; | |
| 60 | ||
| 61 | // Checkout a write connection | |
| 62 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 63 | ||
| 64 | // Collect errorOptions | |
| 65 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 66 | ||
| 67 | // If we have a write command with no callback and w:0 fail | |
| 68 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 69 | 0 | throw new Error("writeConcern requires callback") |
| 70 | } | |
| 71 | ||
| 72 | // Add the documents and decorate them with id's if they have none | |
| 73 | 0 | for(var index = 0, len = docs.length; index < len; ++index) { |
| 74 | 0 | var doc = docs[index]; |
| 75 | ||
| 76 | // Add id to each document if it's not already defined | |
| 77 | 0 | if (!(Buffer.isBuffer(doc)) |
| 78 | && doc['_id'] == null | |
| 79 | && self.db.forceServerObjectId != true | |
| 80 | && options.forceServerObjectId != true) { | |
| 81 | 0 | doc['_id'] = self.pkFactory.createPk(); |
| 82 | } | |
| 83 | } | |
| 84 | ||
| 85 | // Create the write command | |
| 86 | 0 | var write_command = { |
| 87 | insert: namespace | |
| 88 | , writeConcern: errorOptions | |
| 89 | , ordered: !continueOnError | |
| 90 | , documents: docs | |
| 91 | } | |
| 92 | ||
| 93 | // Execute the write command | |
| 94 | 0 | self.db.command(write_command |
| 95 | , { connection:connection | |
| 96 | , checkKeys: true | |
| 97 | , serializeFunctions: serializeFunctions | |
| 98 | , writeCommand: true } | |
| 99 | , function(err, result) { | |
| 100 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 101 | 0 | if(errorOptions.w == 0) return; |
| 102 | 0 | if(callback == null) return; |
| 103 | 0 | if(err != null) { |
| 104 | // Rewrite for backward compatibility | |
| 105 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 106 | // Return the error | |
| 107 | 0 | return callback(err, null); |
| 108 | } | |
| 109 | ||
| 110 | // Result has an error | |
| 111 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 112 | // Map the error | |
| 113 | 0 | var error = utils.toError(result); |
| 114 | // Backwards compatibility mapping | |
| 115 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 116 | // Return the error | |
| 117 | 0 | return callback(error, null); |
| 118 | } | |
| 119 | ||
| 120 | // Return the results for a whole batch | |
| 121 | 0 | callback(null, docs) |
| 122 | }); | |
| 123 | } | |
| 124 | ||
| 125 | // | |
| 126 | // Uses pre 2.6 OP_INSERT wire protocol | |
| 127 | // | |
| 128 | 1 | var insertAll = function insertAll (self, docs, options, callback) { |
| 129 | 0 | if('function' === typeof options) callback = options, options = {}; |
| 130 | 0 | if(options == null) options = {}; |
| 131 | 0 | if(!('function' === typeof callback)) callback = null; |
| 132 | ||
| 133 | // Insert options (flags for insert) | |
| 134 | 0 | var insertFlags = {}; |
| 135 | // If we have a mongodb version >= 1.9.1 support keepGoing attribute | |
| 136 | 0 | if(options['keepGoing'] != null) { |
| 137 | 0 | insertFlags['keepGoing'] = options['keepGoing']; |
| 138 | } | |
| 139 | ||
| 140 | // If we have a mongodb version >= 1.9.1 support keepGoing attribute | |
| 141 | 0 | if(options['continueOnError'] != null) { |
| 142 | 0 | insertFlags['continueOnError'] = options['continueOnError']; |
| 143 | } | |
| 144 | ||
| 145 | // DbName | |
| 146 | 0 | var dbName = options['dbName']; |
| 147 | // If no dbname defined use the db one | |
| 148 | 0 | if(dbName == null) { |
| 149 | 0 | dbName = self.db.databaseName; |
| 150 | } | |
| 151 | ||
| 152 | // Either use override on the function, or go back to default on either the collection | |
| 153 | // level or db | |
| 154 | 0 | if(options['serializeFunctions'] != null) { |
| 155 | 0 | insertFlags['serializeFunctions'] = options['serializeFunctions']; |
| 156 | } else { | |
| 157 | 0 | insertFlags['serializeFunctions'] = self.serializeFunctions; |
| 158 | } | |
| 159 | ||
| 160 | // Get checkKeys value | |
| 161 | 0 | var checkKeys = typeof options.checkKeys != 'boolean' ? true : options.checkKeys; |
| 162 | ||
| 163 | // Pass in options | |
| 164 | 0 | var insertCommand = new InsertCommand( |
| 165 | self.db | |
| 166 | , dbName + "." + self.collectionName, checkKeys, insertFlags); | |
| 167 | ||
| 168 | // Add the documents and decorate them with id's if they have none | |
| 169 | 0 | for(var index = 0, len = docs.length; index < len; ++index) { |
| 170 | 0 | var doc = docs[index]; |
| 171 | ||
| 172 | // Add id to each document if it's not already defined | |
| 173 | 0 | if (!(Buffer.isBuffer(doc)) |
| 174 | && doc['_id'] == null | |
| 175 | && self.db.forceServerObjectId != true | |
| 176 | && options.forceServerObjectId != true) { | |
| 177 | 0 | doc['_id'] = self.pkFactory.createPk(); |
| 178 | } | |
| 179 | ||
| 180 | 0 | insertCommand.add(doc); |
| 181 | } | |
| 182 | ||
| 183 | // Collect errorOptions | |
| 184 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 185 | // Default command options | |
| 186 | 0 | var commandOptions = {}; |
| 187 | // If safe is defined check for error message | |
| 188 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 189 | // Insert options | |
| 190 | 0 | commandOptions['read'] = false; |
| 191 | // If we have safe set set async to false | |
| 192 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 193 | ||
| 194 | // Set safe option | |
| 195 | 0 | commandOptions['safe'] = errorOptions; |
| 196 | // If we have an error option | |
| 197 | 0 | if(typeof errorOptions == 'object') { |
| 198 | 0 | var keys = Object.keys(errorOptions); |
| 199 | 0 | for(var i = 0; i < keys.length; i++) { |
| 200 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 205 | 0 | self.db._executeInsertCommand(insertCommand, commandOptions, function (err, error) { |
| 206 | 0 | error = error && error.documents; |
| 207 | 0 | if(!callback) return; |
| 208 | ||
| 209 | 0 | if (err) { |
| 210 | 0 | callback(err); |
| 211 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 212 | 0 | callback(utils.toError(error[0])); |
| 213 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 214 | 0 | callback(utils.toError(error[0])); |
| 215 | } else { | |
| 216 | 0 | callback(null, docs); |
| 217 | } | |
| 218 | }); | |
| 219 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 220 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 221 | } else { | |
| 222 | // Execute the call without a write concern | |
| 223 | 0 | var result = self.db._executeInsertCommand(insertCommand, commandOptions); |
| 224 | // If no callback just return | |
| 225 | 0 | if(!callback) return; |
| 226 | // If error return error | |
| 227 | 0 | if(result instanceof Error) { |
| 228 | 0 | return callback(result); |
| 229 | } | |
| 230 | ||
| 231 | // Otherwise just return | |
| 232 | 0 | return callback(null, docs); |
| 233 | } | |
| 234 | }; | |
| 235 | ||
| 236 | // *************************************************** | |
| 237 | // Remove function | |
| 238 | // *************************************************** | |
| 239 | 1 | var removeWithWriteCommands = function(self, selector, options, callback) { |
| 240 | 0 | if ('function' === typeof selector) { |
| 241 | 0 | callback = selector; |
| 242 | 0 | selector = options = {}; |
| 243 | 0 | } else if ('function' === typeof options) { |
| 244 | 0 | callback = options; |
| 245 | 0 | options = {}; |
| 246 | } | |
| 247 | ||
| 248 | // Get the intended namespace for the operation | |
| 249 | 0 | var namespace = self.collectionName; |
| 250 | ||
| 251 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 252 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 253 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 254 | } | |
| 255 | ||
| 256 | // Set default empty selector if none | |
| 257 | 0 | selector = selector == null ? {} : selector; |
| 258 | ||
| 259 | // Check if we have passed in continue on error | |
| 260 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 261 | ? options['keepGoing'] : false; | |
| 262 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 263 | ? options['continueOnError'] : continueOnError; | |
| 264 | ||
| 265 | // Do we serialzie functions | |
| 266 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 267 | ? self.serializeFunctions : options.serializeFunctions; | |
| 268 | ||
| 269 | // Checkout a write connection | |
| 270 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 271 | ||
| 272 | // Figure out the value of top | |
| 273 | 0 | var limit = options.single == true ? 1 : 0; |
| 274 | 0 | var upsert = typeof options.upsert == 'boolean' ? options.upsert : false; |
| 275 | ||
| 276 | // Collect errorOptions | |
| 277 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 278 | ||
| 279 | // If we have a write command with no callback and w:0 fail | |
| 280 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 281 | 0 | throw new Error("writeConcern requires callback") |
| 282 | } | |
| 283 | ||
| 284 | // Create the write command | |
| 285 | 0 | var write_command = { |
| 286 | delete: namespace, | |
| 287 | writeConcern: errorOptions, | |
| 288 | ordered: !continueOnError, | |
| 289 | deletes: [{ | |
| 290 | q : selector, | |
| 291 | limit: limit | |
| 292 | }] | |
| 293 | } | |
| 294 | ||
| 295 | // Execute the write command | |
| 296 | 0 | self.db.command(write_command |
| 297 | , { connection:connection | |
| 298 | , checkKeys: true | |
| 299 | , serializeFunctions: serializeFunctions | |
| 300 | , writeCommand: true } | |
| 301 | , function(err, result) { | |
| 302 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 303 | 0 | if(errorOptions.w == 0) return; |
| 304 | 0 | if(callback == null) return; |
| 305 | 0 | if(err != null) { |
| 306 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 307 | // Return the error | |
| 308 | 0 | return callback(err, null); |
| 309 | } | |
| 310 | ||
| 311 | // Result has an error | |
| 312 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 313 | // Map the error | |
| 314 | 0 | var error = utils.toError(result); |
| 315 | // Backwards compatibility mapping | |
| 316 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 317 | // Return the error | |
| 318 | 0 | return callback(error, null); |
| 319 | } | |
| 320 | ||
| 321 | // Backward compatibility format | |
| 322 | 0 | var r = backWardsCompatibiltyResults(result, 'remove'); |
| 323 | // Return the results for a whole batch | |
| 324 | 0 | callback(null, r.n, r) |
| 325 | }); | |
| 326 | } | |
| 327 | ||
| 328 | 1 | var remove = function remove(selector, options, callback) { |
| 329 | 0 | if('function' === typeof options) callback = options, options = null; |
| 330 | 0 | if(options == null) options = {}; |
| 331 | 0 | if(!('function' === typeof callback)) callback = null; |
| 332 | // Get a connection | |
| 333 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 334 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 335 | // If we support write commands let's perform the insert using it | |
| 336 | 0 | if(!useLegacyOps && hasWriteCommands(connection) && !Buffer.isBuffer(selector)) { |
| 337 | 0 | return removeWithWriteCommands(this, selector, options, callback); |
| 338 | } | |
| 339 | ||
| 340 | 0 | if ('function' === typeof selector) { |
| 341 | 0 | callback = selector; |
| 342 | 0 | selector = options = {}; |
| 343 | 0 | } else if ('function' === typeof options) { |
| 344 | 0 | callback = options; |
| 345 | 0 | options = {}; |
| 346 | } | |
| 347 | ||
| 348 | // Ensure options | |
| 349 | 0 | if(options == null) options = {}; |
| 350 | 0 | if(!('function' === typeof callback)) callback = null; |
| 351 | // Ensure we have at least an empty selector | |
| 352 | 0 | selector = selector == null ? {} : selector; |
| 353 | // Set up flags for the command, if we have a single document remove | |
| 354 | 0 | var flags = 0 | (options.single ? 1 : 0); |
| 355 | ||
| 356 | // DbName | |
| 357 | 0 | var dbName = options['dbName']; |
| 358 | // If no dbname defined use the db one | |
| 359 | 0 | if(dbName == null) { |
| 360 | 0 | dbName = this.db.databaseName; |
| 361 | } | |
| 362 | ||
| 363 | // Create a delete command | |
| 364 | 0 | var deleteCommand = new DeleteCommand( |
| 365 | this.db | |
| 366 | , dbName + "." + this.collectionName | |
| 367 | , selector | |
| 368 | , flags); | |
| 369 | ||
| 370 | 0 | var self = this; |
| 371 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 372 | // Execute the command, do not add a callback as it's async | |
| 373 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 374 | // Insert options | |
| 375 | 0 | var commandOptions = {read:false}; |
| 376 | // If we have safe set set async to false | |
| 377 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 378 | // Set safe option | |
| 379 | 0 | commandOptions['safe'] = true; |
| 380 | // If we have an error option | |
| 381 | 0 | if(typeof errorOptions == 'object') { |
| 382 | 0 | var keys = Object.keys(errorOptions); |
| 383 | 0 | for(var i = 0; i < keys.length; i++) { |
| 384 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 389 | 0 | this.db._executeRemoveCommand(deleteCommand, commandOptions, function (err, error) { |
| 390 | 0 | error = error && error.documents; |
| 391 | 0 | if(!callback) return; |
| 392 | ||
| 393 | 0 | if(err) { |
| 394 | 0 | callback(err); |
| 395 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 396 | 0 | callback(utils.toError(error[0])); |
| 397 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 398 | 0 | callback(utils.toError(error[0])); |
| 399 | } else { | |
| 400 | 0 | callback(null, error[0].n); |
| 401 | } | |
| 402 | }); | |
| 403 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 404 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 405 | } else { | |
| 406 | 0 | var result = this.db._executeRemoveCommand(deleteCommand); |
| 407 | // If no callback just return | |
| 408 | 0 | if (!callback) return; |
| 409 | // If error return error | |
| 410 | 0 | if (result instanceof Error) { |
| 411 | 0 | return callback(result); |
| 412 | } | |
| 413 | // Otherwise just return | |
| 414 | 0 | return callback(); |
| 415 | } | |
| 416 | }; | |
| 417 | ||
| 418 | // *************************************************** | |
| 419 | // Save function | |
| 420 | // *************************************************** | |
| 421 | 1 | var save = function save(doc, options, callback) { |
| 422 | 0 | if('function' === typeof options) callback = options, options = null; |
| 423 | 0 | if(options == null) options = {}; |
| 424 | 0 | if(!('function' === typeof callback)) callback = null; |
| 425 | // Throw an error if attempting to perform a bulk operation | |
| 426 | 0 | if(Array.isArray(doc)) throw new Error("doc parameter must be a single document"); |
| 427 | // Extract the id, if we have one we need to do a update command | |
| 428 | 0 | var id = doc['_id']; |
| 429 | 0 | var commandOptions = shared._getWriteConcern(this, options); |
| 430 | ||
| 431 | 0 | if(id != null) { |
| 432 | 0 | commandOptions.upsert = true; |
| 433 | 0 | this.update({ _id: id }, doc, commandOptions, callback); |
| 434 | } else { | |
| 435 | 0 | this.insert(doc, commandOptions, callback && function (err, docs) { |
| 436 | 0 | if(err) return callback(err, null); |
| 437 | ||
| 438 | 0 | if(Array.isArray(docs)) { |
| 439 | 0 | callback(err, docs[0]); |
| 440 | } else { | |
| 441 | 0 | callback(err, docs); |
| 442 | } | |
| 443 | }); | |
| 444 | } | |
| 445 | }; | |
| 446 | ||
| 447 | // *************************************************** | |
| 448 | // Update document function | |
| 449 | // *************************************************** | |
| 450 | 1 | var updateWithWriteCommands = function(self, selector, document, options, callback) { |
| 451 | 0 | if('function' === typeof options) callback = options, options = null; |
| 452 | 0 | if(options == null) options = {}; |
| 453 | 0 | if(!('function' === typeof callback)) callback = null; |
| 454 | ||
| 455 | // Get the intended namespace for the operation | |
| 456 | 0 | var namespace = self.collectionName; |
| 457 | ||
| 458 | // Ensure we have no \x00 bytes in the name causing wrong parsing | |
| 459 | 0 | if(!!~namespace.indexOf("\x00")) { |
| 460 | 0 | return callback(new Error("namespace cannot contain a null character"), null); |
| 461 | } | |
| 462 | ||
| 463 | // If we are not providing a selector or document throw | |
| 464 | 0 | if(selector == null || typeof selector != 'object') |
| 465 | 0 | return callback(new Error("selector must be a valid JavaScript object")); |
| 466 | 0 | if(document == null || typeof document != 'object') |
| 467 | 0 | return callback(new Error("document must be a valid JavaScript object")); |
| 468 | ||
| 469 | // Check if we have passed in continue on error | |
| 470 | 0 | var continueOnError = typeof options['keepGoing'] == 'boolean' |
| 471 | ? options['keepGoing'] : false; | |
| 472 | 0 | continueOnError = typeof options['continueOnError'] == 'boolean' |
| 473 | ? options['continueOnError'] : continueOnError; | |
| 474 | ||
| 475 | // Do we serialzie functions | |
| 476 | 0 | var serializeFunctions = typeof options.serializeFunctions != 'boolean' |
| 477 | ? self.serializeFunctions : options.serializeFunctions; | |
| 478 | ||
| 479 | // Checkout a write connection | |
| 480 | 0 | var connection = self.db.serverConfig.checkoutWriter(); |
| 481 | ||
| 482 | // Figure out the value of top | |
| 483 | 0 | var multi = typeof options.multi == 'boolean' ? options.multi : false; |
| 484 | 0 | var upsert = typeof options.upsert == 'boolean' ? options.upsert : false; |
| 485 | ||
| 486 | // Collect errorOptions | |
| 487 | 0 | var errorOptions = shared._getWriteConcern(self, options); |
| 488 | ||
| 489 | // If we have a write command with no callback and w:0 fail | |
| 490 | 0 | if(errorOptions.w && errorOptions.w != 0 && callback == null) { |
| 491 | 0 | throw new Error("writeConcern requires callback") |
| 492 | } | |
| 493 | ||
| 494 | // Create the write command | |
| 495 | 0 | var write_command = { |
| 496 | update: namespace, | |
| 497 | writeConcern: errorOptions, | |
| 498 | ordered: !continueOnError, | |
| 499 | updates: [{ | |
| 500 | q : selector, | |
| 501 | u: document, | |
| 502 | multi: multi, | |
| 503 | upsert: upsert | |
| 504 | }] | |
| 505 | } | |
| 506 | ||
| 507 | // Check if we have a checkKeys override | |
| 508 | 0 | var checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false; |
| 509 | ||
| 510 | // Execute the write command | |
| 511 | 0 | self.db.command(write_command |
| 512 | , { connection:connection | |
| 513 | , checkKeys: checkKeys | |
| 514 | , serializeFunctions: serializeFunctions | |
| 515 | , writeCommand: true } | |
| 516 | , function(err, result) { | |
| 517 | 0 | if(errorOptions.w == 0 && typeof callback == 'function') return callback(null, null); |
| 518 | 0 | if(errorOptions.w == 0) return; |
| 519 | 0 | if(callback == null) return; |
| 520 | 0 | if(err != null) { |
| 521 | 0 | if(Array.isArray(err.errDetails)) err.code = err.errDetails[0].errCode; |
| 522 | // Return the error | |
| 523 | 0 | return callback(err, null); |
| 524 | } | |
| 525 | ||
| 526 | // Result has an error | |
| 527 | 0 | if(!result.ok && (result.err != null || result.errmsg != null)) { |
| 528 | // Map the error | |
| 529 | 0 | var error = utils.toError(result); |
| 530 | // Backwards compatibility mapping | |
| 531 | 0 | if(Array.isArray(error.errDetails)) error.code = error.errDetails[0].errCode; |
| 532 | // Return the error | |
| 533 | 0 | return callback(error, null); |
| 534 | } | |
| 535 | ||
| 536 | // Backward compatibility format | |
| 537 | 0 | var r = backWardsCompatibiltyResults(result, 'update'); |
| 538 | // Return the results for a whole batch | |
| 539 | 0 | callback(null, r.n, r) |
| 540 | }); | |
| 541 | } | |
| 542 | ||
| 543 | 1 | var backWardsCompatibiltyResults = function(result, op) { |
| 544 | // Upserted | |
| 545 | 0 | var upsertedValue = null; |
| 546 | 0 | var finalResult = null; |
| 547 | 0 | var updatedExisting = true; |
| 548 | ||
| 549 | // We have a single document upserted result | |
| 550 | 0 | if(Array.isArray(result.upserted) || result.upserted != null) { |
| 551 | 0 | updatedExisting = false; |
| 552 | 0 | upsertedValue = result.upserted; |
| 553 | } | |
| 554 | ||
| 555 | // Final result | |
| 556 | 0 | if(op == 'remove' || op == 'insert') { |
| 557 | 0 | finalResult = {ok: true, n: result.n} |
| 558 | } else { | |
| 559 | 0 | finalResult = {ok: true, n: result.n, updatedExisting: updatedExisting} |
| 560 | } | |
| 561 | ||
| 562 | 0 | if(upsertedValue != null) finalResult.upserted = upsertedValue; |
| 563 | 0 | return finalResult; |
| 564 | } | |
| 565 | ||
| 566 | 1 | var update = function update(selector, document, options, callback) { |
| 567 | 0 | if('function' === typeof options) callback = options, options = null; |
| 568 | 0 | if(options == null) options = {}; |
| 569 | 0 | if(!('function' === typeof callback)) callback = null; |
| 570 | ||
| 571 | // Get a connection | |
| 572 | 0 | var connection = this.db.serverConfig.checkoutWriter(); |
| 573 | 0 | var useLegacyOps = options.useLegacyOps == null || options.useLegacyOps == false ? false : true; |
| 574 | // If we support write commands let's perform the insert using it | |
| 575 | 0 | if(!useLegacyOps && hasWriteCommands(connection) && !Buffer.isBuffer(selector) && !Buffer.isBuffer(document)) { |
| 576 | 0 | return updateWithWriteCommands(this, selector, document, options, callback); |
| 577 | } | |
| 578 | ||
| 579 | // DbName | |
| 580 | 0 | var dbName = options['dbName']; |
| 581 | // If no dbname defined use the db one | |
| 582 | 0 | if(dbName == null) { |
| 583 | 0 | dbName = this.db.databaseName; |
| 584 | } | |
| 585 | ||
| 586 | // If we are not providing a selector or document throw | |
| 587 | 0 | if(selector == null || typeof selector != 'object') return callback(new Error("selector must be a valid JavaScript object")); |
| 588 | 0 | if(document == null || typeof document != 'object') return callback(new Error("document must be a valid JavaScript object")); |
| 589 | ||
| 590 | // Either use override on the function, or go back to default on either the collection | |
| 591 | // level or db | |
| 592 | 0 | if(options['serializeFunctions'] != null) { |
| 593 | 0 | options['serializeFunctions'] = options['serializeFunctions']; |
| 594 | } else { | |
| 595 | 0 | options['serializeFunctions'] = this.serializeFunctions; |
| 596 | } | |
| 597 | ||
| 598 | // Build the options command | |
| 599 | 0 | var updateCommand = new UpdateCommand( |
| 600 | this.db | |
| 601 | , dbName + "." + this.collectionName | |
| 602 | , selector | |
| 603 | , document | |
| 604 | , options); | |
| 605 | ||
| 606 | 0 | var self = this; |
| 607 | // Unpack the error options if any | |
| 608 | 0 | var errorOptions = shared._getWriteConcern(this, options); |
| 609 | // If safe is defined check for error message | |
| 610 | 0 | if(shared._hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 611 | // Insert options | |
| 612 | 0 | var commandOptions = {read:false}; |
| 613 | // If we have safe set set async to false | |
| 614 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 615 | // Set safe option | |
| 616 | 0 | commandOptions['safe'] = errorOptions; |
| 617 | // If we have an error option | |
| 618 | 0 | if(typeof errorOptions == 'object') { |
| 619 | 0 | var keys = Object.keys(errorOptions); |
| 620 | 0 | for(var i = 0; i < keys.length; i++) { |
| 621 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 622 | } | |
| 623 | } | |
| 624 | ||
| 625 | // Execute command with safe options (rolls up both command and safe command into one and executes them on the same connection) | |
| 626 | 0 | this.db._executeUpdateCommand(updateCommand, commandOptions, function (err, error) { |
| 627 | 0 | error = error && error.documents; |
| 628 | 0 | if(!callback) return; |
| 629 | ||
| 630 | 0 | if(err) { |
| 631 | 0 | callback(err); |
| 632 | 0 | } else if(error[0].err || error[0].errmsg) { |
| 633 | 0 | callback(utils.toError(error[0])); |
| 634 | 0 | } else if(error[0].jnote || error[0].wnote || error[0].wtimeout) { |
| 635 | 0 | callback(utils.toError(error[0])); |
| 636 | } else { | |
| 637 | 0 | callback(null, error[0].n, error[0]); |
| 638 | } | |
| 639 | }); | |
| 640 | 0 | } else if(shared._hasWriteConcern(errorOptions) && callback == null) { |
| 641 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 642 | } else { | |
| 643 | // Execute update | |
| 644 | 0 | var result = this.db._executeUpdateCommand(updateCommand); |
| 645 | // If no callback just return | |
| 646 | 0 | if (!callback) return; |
| 647 | // If error return error | |
| 648 | 0 | if (result instanceof Error) { |
| 649 | 0 | return callback(result); |
| 650 | } | |
| 651 | // Otherwise just return | |
| 652 | 0 | return callback(); |
| 653 | } | |
| 654 | }; | |
| 655 | ||
| 656 | // *************************************************** | |
| 657 | // findAndModify function | |
| 658 | // *************************************************** | |
| 659 | 1 | var findAndModify = function findAndModify (query, sort, doc, options, callback) { |
| 660 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 661 | 0 | callback = args.pop(); |
| 662 | 0 | sort = args.length ? args.shift() || [] : []; |
| 663 | 0 | doc = args.length ? args.shift() : null; |
| 664 | 0 | options = args.length ? args.shift() || {} : {}; |
| 665 | 0 | var self = this; |
| 666 | ||
| 667 | 0 | var queryObject = { |
| 668 | 'findandmodify': this.collectionName | |
| 669 | , 'query': query | |
| 670 | , 'sort': utils.formattedOrderClause(sort) | |
| 671 | }; | |
| 672 | ||
| 673 | 0 | queryObject.new = options.new ? 1 : 0; |
| 674 | 0 | queryObject.remove = options.remove ? 1 : 0; |
| 675 | 0 | queryObject.upsert = options.upsert ? 1 : 0; |
| 676 | ||
| 677 | 0 | if (options.fields) { |
| 678 | 0 | queryObject.fields = options.fields; |
| 679 | } | |
| 680 | ||
| 681 | 0 | if (doc && !options.remove) { |
| 682 | 0 | queryObject.update = doc; |
| 683 | } | |
| 684 | ||
| 685 | // Checkout a write connection | |
| 686 | 0 | options.connection = self.db.serverConfig.checkoutWriter(); |
| 687 | ||
| 688 | // Either use override on the function, or go back to default on either the collection | |
| 689 | // level or db | |
| 690 | 0 | if(options['serializeFunctions'] != null) { |
| 691 | 0 | options['serializeFunctions'] = options['serializeFunctions']; |
| 692 | } else { | |
| 693 | 0 | options['serializeFunctions'] = this.serializeFunctions; |
| 694 | } | |
| 695 | ||
| 696 | // No check on the documents | |
| 697 | 0 | options.checkKeys = false |
| 698 | ||
| 699 | // Execute the command | |
| 700 | 0 | this.db.command(queryObject |
| 701 | , options, function(err, result) { | |
| 702 | 0 | if(err) return callback(err, null); |
| 703 | 0 | return callback(null, result.value, result); |
| 704 | }); | |
| 705 | } | |
| 706 | ||
| 707 | // *************************************************** | |
| 708 | // findAndRemove function | |
| 709 | // *************************************************** | |
| 710 | 1 | var findAndRemove = function(query, sort, options, callback) { |
| 711 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 712 | 0 | callback = args.pop(); |
| 713 | 0 | sort = args.length ? args.shift() || [] : []; |
| 714 | 0 | options = args.length ? args.shift() || {} : {}; |
| 715 | // Add the remove option | |
| 716 | 0 | options['remove'] = true; |
| 717 | // Execute the callback | |
| 718 | 0 | this.findAndModify(query, sort, null, options, callback); |
| 719 | } | |
| 720 | ||
| 721 | // Map methods | |
| 722 | 1 | exports.insert = insert; |
| 723 | 1 | exports.remove = remove; |
| 724 | 1 | exports.save = save; |
| 725 | 1 | exports.update = update; |
| 726 | 1 | exports.findAndModify = findAndModify; |
| 727 | 1 | exports.findAndRemove = findAndRemove; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var shared = require('./shared') |
| 2 | , utils = require('../utils'); | |
| 3 | ||
| 4 | 1 | var geoNear = function geoNear(x, y, options, callback) { |
| 5 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 6 | 0 | callback = args.pop(); |
| 7 | // Fetch all commands | |
| 8 | 0 | options = args.length ? args.shift() || {} : {}; |
| 9 | ||
| 10 | // Build command object | |
| 11 | 0 | var commandObject = { |
| 12 | geoNear:this.collectionName, | |
| 13 | near: [x, y] | |
| 14 | } | |
| 15 | ||
| 16 | // Decorate object if any with known properties | |
| 17 | 0 | if(options['num'] != null) commandObject['num'] = options['num']; |
| 18 | 0 | if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; |
| 19 | 0 | if(options['distanceMultiplier'] != null) commandObject['distanceMultiplier'] = options['distanceMultiplier']; |
| 20 | 0 | if(options['query'] != null) commandObject['query'] = options['query']; |
| 21 | 0 | if(options['spherical'] != null) commandObject['spherical'] = options['spherical']; |
| 22 | 0 | if(options['uniqueDocs'] != null) commandObject['uniqueDocs'] = options['uniqueDocs']; |
| 23 | 0 | if(options['includeLocs'] != null) commandObject['includeLocs'] = options['includeLocs']; |
| 24 | ||
| 25 | // Ensure we have the right read preference inheritance | |
| 26 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 27 | ||
| 28 | // Execute the command | |
| 29 | 0 | this.db.command(commandObject, options, function (err, res) { |
| 30 | 0 | if (err) { |
| 31 | 0 | callback(err); |
| 32 | 0 | } else if (res.err || res.errmsg) { |
| 33 | 0 | callback(utils.toError(res)); |
| 34 | } else { | |
| 35 | // should we only be returning res.results here? Not sure if the user | |
| 36 | // should see the other return information | |
| 37 | 0 | callback(null, res); |
| 38 | } | |
| 39 | }); | |
| 40 | } | |
| 41 | ||
| 42 | 1 | var geoHaystackSearch = function geoHaystackSearch(x, y, options, callback) { |
| 43 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 44 | 0 | callback = args.pop(); |
| 45 | // Fetch all commands | |
| 46 | 0 | options = args.length ? args.shift() || {} : {}; |
| 47 | ||
| 48 | // Build command object | |
| 49 | 0 | var commandObject = { |
| 50 | geoSearch:this.collectionName, | |
| 51 | near: [x, y] | |
| 52 | } | |
| 53 | ||
| 54 | // Decorate object if any with known properties | |
| 55 | 0 | if(options['maxDistance'] != null) commandObject['maxDistance'] = options['maxDistance']; |
| 56 | 0 | if(options['query'] != null) commandObject['search'] = options['query']; |
| 57 | 0 | if(options['search'] != null) commandObject['search'] = options['search']; |
| 58 | 0 | if(options['limit'] != null) commandObject['limit'] = options['limit']; |
| 59 | ||
| 60 | // Ensure we have the right read preference inheritance | |
| 61 | 0 | options.readPreference = shared._getReadConcern(this, options); |
| 62 | ||
| 63 | // Execute the command | |
| 64 | 0 | this.db.command(commandObject, options, function (err, res) { |
| 65 | 0 | if (err) { |
| 66 | 0 | callback(err); |
| 67 | 0 | } else if (res.err || res.errmsg) { |
| 68 | 0 | callback(utils.toError(res)); |
| 69 | } else { | |
| 70 | // should we only be returning res.results here? Not sure if the user | |
| 71 | // should see the other return information | |
| 72 | 0 | callback(null, res); |
| 73 | } | |
| 74 | }); | |
| 75 | } | |
| 76 | ||
| 77 | 1 | exports.geoNear = geoNear; |
| 78 | 1 | exports.geoHaystackSearch = geoHaystackSearch; |
| 79 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var _getWriteConcern = require('./shared')._getWriteConcern; |
| 2 | ||
| 3 | 1 | var createIndex = function createIndex (fieldOrSpec, options, callback) { |
| 4 | // Clean up call | |
| 5 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 6 | 0 | callback = args.pop(); |
| 7 | 0 | options = args.length ? args.shift() || {} : {}; |
| 8 | 0 | options = typeof callback === 'function' ? options : callback; |
| 9 | 0 | options = options == null ? {} : options; |
| 10 | ||
| 11 | // Collect errorOptions | |
| 12 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 13 | // Execute create index | |
| 14 | 0 | this.db.createIndex(this.collectionName, fieldOrSpec, options, callback); |
| 15 | }; | |
| 16 | ||
| 17 | 1 | var indexExists = function indexExists(indexes, callback) { |
| 18 | 0 | this.indexInformation(function(err, indexInformation) { |
| 19 | // If we have an error return | |
| 20 | 0 | if(err != null) return callback(err, null); |
| 21 | // Let's check for the index names | |
| 22 | 0 | if(Array.isArray(indexes)) { |
| 23 | 0 | for(var i = 0; i < indexes.length; i++) { |
| 24 | 0 | if(indexInformation[indexes[i]] == null) { |
| 25 | 0 | return callback(null, false); |
| 26 | } | |
| 27 | } | |
| 28 | ||
| 29 | // All keys found return true | |
| 30 | 0 | return callback(null, true); |
| 31 | } else { | |
| 32 | 0 | return callback(null, indexInformation[indexes] != null); |
| 33 | } | |
| 34 | }); | |
| 35 | } | |
| 36 | ||
| 37 | 1 | var dropAllIndexes = function dropIndexes (callback) { |
| 38 | 0 | this.db.dropIndex(this.collectionName, '*', function (err, result) { |
| 39 | 0 | if(err) return callback(err, false); |
| 40 | 0 | callback(null, true); |
| 41 | }); | |
| 42 | }; | |
| 43 | ||
| 44 | 1 | var indexInformation = function indexInformation (options, callback) { |
| 45 | // Unpack calls | |
| 46 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 47 | 0 | callback = args.pop(); |
| 48 | 0 | options = args.length ? args.shift() || {} : {}; |
| 49 | // Call the index information | |
| 50 | 0 | this.db.indexInformation(this.collectionName, options, callback); |
| 51 | }; | |
| 52 | ||
| 53 | 1 | var ensureIndex = function ensureIndex (fieldOrSpec, options, callback) { |
| 54 | // Clean up call | |
| 55 | 0 | if (typeof callback === 'undefined' && typeof options === 'function') { |
| 56 | 0 | callback = options; |
| 57 | 0 | options = {}; |
| 58 | } | |
| 59 | ||
| 60 | 0 | if (options == null) { |
| 61 | 0 | options = {}; |
| 62 | } | |
| 63 | ||
| 64 | // Execute create index | |
| 65 | 0 | this.db.ensureIndex(this.collectionName, fieldOrSpec, options, callback); |
| 66 | }; | |
| 67 | ||
| 68 | 1 | exports.createIndex = createIndex; |
| 69 | 1 | exports.indexExists = indexExists; |
| 70 | 1 | exports.dropAllIndexes = dropAllIndexes; |
| 71 | 1 | exports.indexInformation = indexInformation; |
| 72 | 1 | exports.ensureIndex = ensureIndex; |
| 73 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ObjectID = require('bson').ObjectID |
| 2 | , Scope = require('../scope').Scope | |
| 3 | , shared = require('./shared') | |
| 4 | , utils = require('../utils'); | |
| 5 | ||
| 6 | 1 | var testForFields = { |
| 7 | limit: 1, sort: 1, fields:1, skip: 1, hint: 1, explain: 1, snapshot: 1, timeout: 1, tailable: 1, tailableRetryInterval: 1 | |
| 8 | , numberOfRetries: 1, awaitdata: 1, exhaust: 1, batchSize: 1, returnKey: 1, maxScan: 1, min: 1, max: 1, showDiskLoc: 1 | |
| 9 | , comment: 1, raw: 1, readPreference: 1, partial: 1, read: 1, dbName: 1, oplogReplay: 1 | |
| 10 | }; | |
| 11 | ||
| 12 | // | |
| 13 | // Find method | |
| 14 | // | |
| 15 | 1 | var find = function find () { |
| 16 | 0 | var options |
| 17 | , args = Array.prototype.slice.call(arguments, 0) | |
| 18 | , has_callback = typeof args[args.length - 1] === 'function' | |
| 19 | , has_weird_callback = typeof args[0] === 'function' | |
| 20 | , callback = has_callback ? args.pop() : (has_weird_callback ? args.shift() : null) | |
| 21 | , len = args.length | |
| 22 | , selector = len >= 1 ? args[0] : {} | |
| 23 | , fields = len >= 2 ? args[1] : undefined; | |
| 24 | ||
| 25 | 0 | if(len === 1 && has_weird_callback) { |
| 26 | // backwards compat for callback?, options case | |
| 27 | 0 | selector = {}; |
| 28 | 0 | options = args[0]; |
| 29 | } | |
| 30 | ||
| 31 | 0 | if(len === 2 && !Array.isArray(fields)) { |
| 32 | 0 | var fieldKeys = Object.getOwnPropertyNames(fields); |
| 33 | 0 | var is_option = false; |
| 34 | ||
| 35 | 0 | for(var i = 0; i < fieldKeys.length; i++) { |
| 36 | 0 | if(testForFields[fieldKeys[i]] != null) { |
| 37 | 0 | is_option = true; |
| 38 | 0 | break; |
| 39 | } | |
| 40 | } | |
| 41 | ||
| 42 | 0 | if(is_option) { |
| 43 | 0 | options = fields; |
| 44 | 0 | fields = undefined; |
| 45 | } else { | |
| 46 | 0 | options = {}; |
| 47 | } | |
| 48 | 0 | } else if(len === 2 && Array.isArray(fields) && !Array.isArray(fields[0])) { |
| 49 | 0 | var newFields = {}; |
| 50 | // Rewrite the array | |
| 51 | 0 | for(var i = 0; i < fields.length; i++) { |
| 52 | 0 | newFields[fields[i]] = 1; |
| 53 | } | |
| 54 | // Set the fields | |
| 55 | 0 | fields = newFields; |
| 56 | } | |
| 57 | ||
| 58 | 0 | if(3 === len) { |
| 59 | 0 | options = args[2]; |
| 60 | } | |
| 61 | ||
| 62 | // Ensure selector is not null | |
| 63 | 0 | selector = selector == null ? {} : selector; |
| 64 | // Validate correctness off the selector | |
| 65 | 0 | var object = selector; |
| 66 | 0 | if(Buffer.isBuffer(object)) { |
| 67 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 68 | 0 | if(object_size != object.length) { |
| 69 | 0 | var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 70 | 0 | error.name = 'MongoError'; |
| 71 | 0 | throw error; |
| 72 | } | |
| 73 | } | |
| 74 | ||
| 75 | // Validate correctness of the field selector | |
| 76 | 0 | var object = fields; |
| 77 | 0 | if(Buffer.isBuffer(object)) { |
| 78 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 79 | 0 | if(object_size != object.length) { |
| 80 | 0 | var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 81 | 0 | error.name = 'MongoError'; |
| 82 | 0 | throw error; |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | // Check special case where we are using an objectId | |
| 87 | 0 | if(selector instanceof ObjectID || (selector != null && selector._bsontype == 'ObjectID')) { |
| 88 | 0 | selector = {_id:selector}; |
| 89 | } | |
| 90 | ||
| 91 | // If it's a serialized fields field we need to just let it through | |
| 92 | // user be warned it better be good | |
| 93 | 0 | if(options && options.fields && !(Buffer.isBuffer(options.fields))) { |
| 94 | 0 | fields = {}; |
| 95 | ||
| 96 | 0 | if(Array.isArray(options.fields)) { |
| 97 | 0 | if(!options.fields.length) { |
| 98 | 0 | fields['_id'] = 1; |
| 99 | } else { | |
| 100 | 0 | for (var i = 0, l = options.fields.length; i < l; i++) { |
| 101 | 0 | fields[options.fields[i]] = 1; |
| 102 | } | |
| 103 | } | |
| 104 | } else { | |
| 105 | 0 | fields = options.fields; |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | 0 | if (!options) options = {}; |
| 110 | 0 | options.skip = len > 3 ? args[2] : options.skip ? options.skip : 0; |
| 111 | 0 | options.limit = len > 3 ? args[3] : options.limit ? options.limit : 0; |
| 112 | 0 | options.raw = options.raw != null && typeof options.raw === 'boolean' ? options.raw : this.raw; |
| 113 | 0 | options.hint = options.hint != null ? shared.normalizeHintField(options.hint) : this.internalHint; |
| 114 | 0 | options.timeout = len == 5 ? args[4] : typeof options.timeout === 'undefined' ? undefined : options.timeout; |
| 115 | // If we have overridden slaveOk otherwise use the default db setting | |
| 116 | 0 | options.slaveOk = options.slaveOk != null ? options.slaveOk : this.db.slaveOk; |
| 117 | ||
| 118 | // Set option | |
| 119 | 0 | var o = options; |
| 120 | // Support read/readPreference | |
| 121 | 0 | if(o["read"] != null) o["readPreference"] = o["read"]; |
| 122 | // Set the read preference | |
| 123 | 0 | o.read = o["readPreference"] ? o.readPreference : this.readPreference; |
| 124 | // Adjust slave ok if read preference is secondary or secondary only | |
| 125 | 0 | if(o.read == "secondary" || o.read == "secondaryOnly") options.slaveOk = true; |
| 126 | ||
| 127 | // Set the selector | |
| 128 | 0 | o.selector = selector; |
| 129 | ||
| 130 | // Create precursor | |
| 131 | 0 | var scope = new Scope(this, {}, fields, o); |
| 132 | // Callback for backward compatibility | |
| 133 | 0 | if(callback) return callback(null, scope.find(selector)); |
| 134 | // Return the pre cursor object | |
| 135 | 0 | return scope.find(selector); |
| 136 | }; | |
| 137 | ||
| 138 | 1 | var findOne = function findOne () { |
| 139 | 0 | var self = this; |
| 140 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 141 | 0 | var callback = args.pop(); |
| 142 | 0 | var cursor = this.find.apply(this, args).limit(-1).batchSize(1); |
| 143 | ||
| 144 | // Return the item | |
| 145 | 0 | cursor.nextObject(function(err, item) { |
| 146 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 147 | 0 | callback(null, item); |
| 148 | }); | |
| 149 | }; | |
| 150 | ||
| 151 | ||
| 152 | 1 | exports.find = find; |
| 153 | 1 | exports.findOne = findOne; |
| Line | Hits | Source |
|---|---|---|
| 1 | // *************************************************** | |
| 2 | // Write concerns | |
| 3 | // *************************************************** | |
| 4 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 5 | 0 | return errorOptions == true |
| 6 | || errorOptions.w > 0 | |
| 7 | || errorOptions.w == 'majority' | |
| 8 | || errorOptions.j == true | |
| 9 | || errorOptions.journal == true | |
| 10 | || errorOptions.fsync == true | |
| 11 | } | |
| 12 | ||
| 13 | 1 | var _setWriteConcernHash = function(options) { |
| 14 | 0 | var finalOptions = {}; |
| 15 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 16 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 17 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 18 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 19 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 20 | 0 | return finalOptions; |
| 21 | } | |
| 22 | ||
| 23 | 1 | var _getWriteConcern = function(self, options) { |
| 24 | // Final options | |
| 25 | 0 | var finalOptions = {w:1}; |
| 26 | // Local options verification | |
| 27 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 28 | 0 | finalOptions = _setWriteConcernHash(options); |
| 29 | 0 | } else if(typeof options.safe == "boolean") { |
| 30 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 31 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 32 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 33 | 0 | } else if(self.opts.w != null || typeof self.opts.j == 'boolean' || typeof self.opts.journal == 'boolean' || typeof self.opts.fsync == 'boolean') { |
| 34 | 0 | finalOptions = _setWriteConcernHash(self.opts); |
| 35 | 0 | } else if(typeof self.opts.safe == "boolean") { |
| 36 | 0 | finalOptions = {w: (self.opts.safe ? 1 : 0)}; |
| 37 | 0 | } else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') { |
| 38 | 0 | finalOptions = _setWriteConcernHash(self.db.safe); |
| 39 | 0 | } else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') { |
| 40 | 0 | finalOptions = _setWriteConcernHash(self.db.options); |
| 41 | 0 | } else if(typeof self.db.safe == "boolean") { |
| 42 | 0 | finalOptions = {w: (self.db.safe ? 1 : 0)}; |
| 43 | } | |
| 44 | ||
| 45 | // Ensure we don't have an invalid combination of write concerns | |
| 46 | 0 | if(finalOptions.w < 1 |
| 47 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 48 | ||
| 49 | // Return the options | |
| 50 | 0 | return finalOptions; |
| 51 | } | |
| 52 | ||
| 53 | 1 | var _getReadConcern = function(self, options) { |
| 54 | 0 | if(options.readPreference) return options.readPreference; |
| 55 | 0 | if(self.readPreference) return self.readPreference; |
| 56 | 0 | if(self.db.readPreference) return self.readPreference; |
| 57 | 0 | return 'primary'; |
| 58 | } | |
| 59 | ||
| 60 | /** | |
| 61 | * @ignore | |
| 62 | */ | |
| 63 | 1 | var checkCollectionName = function checkCollectionName (collectionName) { |
| 64 | 0 | if('string' !== typeof collectionName) { |
| 65 | 0 | throw Error("collection name must be a String"); |
| 66 | } | |
| 67 | ||
| 68 | 0 | if(!collectionName || collectionName.indexOf('..') != -1) { |
| 69 | 0 | throw Error("collection names cannot be empty"); |
| 70 | } | |
| 71 | ||
| 72 | 0 | if(collectionName.indexOf('$') != -1 && |
| 73 | collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null) { | |
| 74 | 0 | throw Error("collection names must not contain '$'"); |
| 75 | } | |
| 76 | ||
| 77 | 0 | if(collectionName.match(/^\.|\.$/) != null) { |
| 78 | 0 | throw Error("collection names must not start or end with '.'"); |
| 79 | } | |
| 80 | ||
| 81 | // Validate that we are not passing 0x00 in the colletion name | |
| 82 | 0 | if(!!~collectionName.indexOf("\x00")) { |
| 83 | 0 | throw new Error("collection names cannot contain a null character"); |
| 84 | } | |
| 85 | }; | |
| 86 | ||
| 87 | ||
| 88 | /** | |
| 89 | * Normalizes a `hint` argument. | |
| 90 | * | |
| 91 | * @param {String|Object|Array} hint | |
| 92 | * @return {Object} | |
| 93 | * @api private | |
| 94 | */ | |
| 95 | 1 | var normalizeHintField = function normalizeHintField(hint) { |
| 96 | 0 | var finalHint = null; |
| 97 | ||
| 98 | 0 | if(typeof hint == 'string') { |
| 99 | 0 | finalHint = hint; |
| 100 | 0 | } else if(Array.isArray(hint)) { |
| 101 | 0 | finalHint = {}; |
| 102 | ||
| 103 | 0 | hint.forEach(function(param) { |
| 104 | 0 | finalHint[param] = 1; |
| 105 | }); | |
| 106 | 0 | } else if(hint != null && typeof hint == 'object') { |
| 107 | 0 | finalHint = {}; |
| 108 | 0 | for (var name in hint) { |
| 109 | 0 | finalHint[name] = hint[name]; |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | 0 | return finalHint; |
| 114 | }; | |
| 115 | ||
| 116 | 1 | exports._getWriteConcern = _getWriteConcern; |
| 117 | 1 | exports._hasWriteConcern = _hasWriteConcern; |
| 118 | 1 | exports._getReadConcern = _getReadConcern; |
| 119 | 1 | exports.checkCollectionName = checkCollectionName; |
| 120 | 1 | exports.normalizeHintField = normalizeHintField; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('bson').Long |
| 2 | , GetMoreCommand = require('./commands/get_more_command').GetMoreCommand; | |
| 3 | ||
| 4 | 1 | var CommandCursor = function(db, collection, command, options) { |
| 5 | // Ensure empty options if no options passed | |
| 6 | 0 | options = options || {}; |
| 7 | ||
| 8 | // Default cursor id is 0 | |
| 9 | 0 | var cursorId = Long.fromInt(0); |
| 10 | 0 | var zeroCursor = Long.fromInt(0); |
| 11 | 0 | var state = 'init'; |
| 12 | ||
| 13 | // Hardcode batch size | |
| 14 | 0 | command.cursor.batchSize = 1; |
| 15 | ||
| 16 | // BatchSize | |
| 17 | 0 | var batchSize = command.cursor.batchSize || 0; |
| 18 | 0 | var raw = options.raw || false; |
| 19 | 0 | var readPreference = options.readPreference || 'primary'; |
| 20 | ||
| 21 | // Checkout a connection | |
| 22 | 0 | var connection = db.serverConfig.checkoutReader(readPreference); |
| 23 | // MaxTimeMS | |
| 24 | 0 | var maxTimeMS = options.maxTimeMS; |
| 25 | ||
| 26 | // Contains all the items | |
| 27 | 0 | var items = null; |
| 28 | ||
| 29 | // Execute getmore | |
| 30 | 0 | var getMore = function(callback) { |
| 31 | // Resolve more of the cursor using the getMore command | |
| 32 | 0 | var getMoreCommand = new GetMoreCommand(db |
| 33 | , db.databaseName + "." + collection.collectionName | |
| 34 | , batchSize | |
| 35 | , cursorId | |
| 36 | ); | |
| 37 | ||
| 38 | // Set up options | |
| 39 | 0 | var command_options = { connection:connection }; |
| 40 | ||
| 41 | // Execute the getMore Command | |
| 42 | 0 | db._executeQueryCommand(getMoreCommand, command_options, function(err, result) { |
| 43 | 0 | if(err) { |
| 44 | 0 | items = []; |
| 45 | 0 | state = 'closed'; |
| 46 | 0 | return callback(err); |
| 47 | } | |
| 48 | ||
| 49 | // Return all the documents | |
| 50 | 0 | callback(null, result); |
| 51 | }); | |
| 52 | } | |
| 53 | ||
| 54 | 0 | var exhaustGetMore = function(callback) { |
| 55 | 0 | getMore(function(err, result) { |
| 56 | 0 | if(err) { |
| 57 | 0 | items = []; |
| 58 | 0 | state = 'closed'; |
| 59 | 0 | return callback(err, null); |
| 60 | } | |
| 61 | ||
| 62 | // Add the items | |
| 63 | 0 | items = items.concat(result.documents); |
| 64 | ||
| 65 | // Set the cursor id | |
| 66 | 0 | cursorId = result.cursorId; |
| 67 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 68 | ||
| 69 | // If the cursor is done | |
| 70 | 0 | if(result.cursorId.equals(zeroCursor)) { |
| 71 | 0 | return callback(null, items); |
| 72 | } | |
| 73 | ||
| 74 | // Check the cursor id | |
| 75 | 0 | exhaustGetMore(callback); |
| 76 | }); | |
| 77 | } | |
| 78 | ||
| 79 | 0 | var exhaustGetMoreEach = function(callback) { |
| 80 | 0 | getMore(function(err, result) { |
| 81 | 0 | if(err) { |
| 82 | 0 | items = []; |
| 83 | 0 | state = 'closed'; |
| 84 | 0 | return callback(err, null); |
| 85 | } | |
| 86 | ||
| 87 | // Add the items | |
| 88 | 0 | items = result.documents; |
| 89 | ||
| 90 | // Emit all the items in the first batch | |
| 91 | 0 | while(items.length > 0) { |
| 92 | 0 | callback(null, items.shift()); |
| 93 | } | |
| 94 | ||
| 95 | // Set the cursor id | |
| 96 | 0 | cursorId = result.cursorId; |
| 97 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 98 | ||
| 99 | // If the cursor is done | |
| 100 | 0 | if(result.cursorId.equals(zeroCursor)) { |
| 101 | 0 | state = "closed"; |
| 102 | 0 | return callback(null, null); |
| 103 | } | |
| 104 | ||
| 105 | // Check the cursor id | |
| 106 | 0 | exhaustGetMoreEach(callback); |
| 107 | }); | |
| 108 | } | |
| 109 | ||
| 110 | // | |
| 111 | // Get all the elements | |
| 112 | // | |
| 113 | 0 | this.get = function(options, callback) { |
| 114 | 0 | if(typeof options == 'function') { |
| 115 | 0 | callback = options; |
| 116 | 0 | options = {}; |
| 117 | } | |
| 118 | ||
| 119 | // Set the connection to the passed in one if it's provided | |
| 120 | 0 | connection = options.connection ? options.connection : connection; |
| 121 | ||
| 122 | // Command options | |
| 123 | 0 | var _options = {connection:connection}; |
| 124 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 125 | ||
| 126 | // Execute the internal command first | |
| 127 | 0 | db.command(command, _options, function(err, result) { |
| 128 | 0 | if(err) { |
| 129 | 0 | state = 'closed'; |
| 130 | 0 | return callback(err, null); |
| 131 | } | |
| 132 | ||
| 133 | // Retrieve the cursor id | |
| 134 | 0 | cursorId = result.cursor.id; |
| 135 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 136 | ||
| 137 | // Validate cursorId | |
| 138 | 0 | if(cursorId.equals(zeroCursor)) { |
| 139 | 0 | return callback(null, result.cursor.firstBatch); |
| 140 | }; | |
| 141 | ||
| 142 | // Add to the items | |
| 143 | 0 | items = result.cursor.firstBatch; |
| 144 | // Execute the getMore | |
| 145 | 0 | exhaustGetMore(callback); |
| 146 | }); | |
| 147 | } | |
| 148 | ||
| 149 | // | |
| 150 | // Iterate over all the items | |
| 151 | // | |
| 152 | 0 | this.each = function(options, callback) { |
| 153 | 0 | if(typeof options == 'function') { |
| 154 | 0 | callback = options; |
| 155 | 0 | options = {}; |
| 156 | } | |
| 157 | ||
| 158 | // If it's a closed cursor return error | |
| 159 | 0 | if(this.isClosed()) return callback(new Error("cursor is closed")); |
| 160 | // Set the connection to the passed in one if it's provided | |
| 161 | 0 | connection = options.connection ? options.connection : connection; |
| 162 | ||
| 163 | // Command options | |
| 164 | 0 | var _options = {connection:connection}; |
| 165 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 166 | ||
| 167 | // Execute the internal command first | |
| 168 | 0 | db.command(command, _options, function(err, result) { |
| 169 | 0 | if(err) { |
| 170 | 0 | state = 'closed'; |
| 171 | 0 | return callback(err, null); |
| 172 | } | |
| 173 | ||
| 174 | // Get all the items | |
| 175 | 0 | items = result.cursor.firstBatch; |
| 176 | ||
| 177 | // Emit all the items in the first batch | |
| 178 | 0 | while(items.length > 0) { |
| 179 | 0 | callback(null, items.shift()); |
| 180 | } | |
| 181 | ||
| 182 | // Retrieve the cursor id | |
| 183 | 0 | cursorId = result.cursor.id; |
| 184 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 185 | ||
| 186 | // If no cursor we just finish up the current batch of items | |
| 187 | 0 | if(cursorId.equals(zeroCursor)) { |
| 188 | 0 | state = 'closed'; |
| 189 | 0 | return callback(null, null); |
| 190 | } | |
| 191 | ||
| 192 | // Emit each until no more getMore's | |
| 193 | 0 | exhaustGetMoreEach(callback); |
| 194 | }); | |
| 195 | } | |
| 196 | ||
| 197 | // | |
| 198 | // Get the next object | |
| 199 | // | |
| 200 | 0 | this.next = function(options, callback) { |
| 201 | 0 | if(typeof options == 'function') { |
| 202 | 0 | callback = options; |
| 203 | 0 | options = {}; |
| 204 | } | |
| 205 | ||
| 206 | // If it's a closed cursor return error | |
| 207 | 0 | if(this.isClosed()) return callback(new Error("cursor is closed")); |
| 208 | ||
| 209 | // Set the connection to the passed in one if it's provided | |
| 210 | 0 | connection = options.connection ? options.connection : connection; |
| 211 | ||
| 212 | // Command options | |
| 213 | 0 | var _options = {connection:connection}; |
| 214 | 0 | if(typeof maxTimeMS == 'number') _options.maxTimeMS = maxTimeMS; |
| 215 | ||
| 216 | // Execute the internal command first | |
| 217 | 0 | if(!items) { |
| 218 | 0 | db.command(command, _options, function(err, result) { |
| 219 | 0 | if(err) { |
| 220 | 0 | state = 'closed'; |
| 221 | 0 | return callback(err, null); |
| 222 | } | |
| 223 | ||
| 224 | // Retrieve the cursor id | |
| 225 | 0 | cursorId = result.cursor.id; |
| 226 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 227 | // Get the first batch results | |
| 228 | 0 | items = result.cursor.firstBatch; |
| 229 | // We have items return the first one | |
| 230 | 0 | if(items.length > 0) { |
| 231 | 0 | callback(null, items.shift()); |
| 232 | } else { | |
| 233 | 0 | state = 'closed'; |
| 234 | 0 | callback(null, null); |
| 235 | } | |
| 236 | }); | |
| 237 | 0 | } else if(items.length > 0) { |
| 238 | 0 | callback(null, items.shift()); |
| 239 | 0 | } else if(items.length == 0 && cursorId.equals(zeroCursor)) { |
| 240 | 0 | state = 'closed'; |
| 241 | 0 | callback(null, null); |
| 242 | } else { | |
| 243 | // Execute a getMore | |
| 244 | 0 | getMore(function(err, result) { |
| 245 | 0 | if(err) { |
| 246 | 0 | state = 'closed'; |
| 247 | 0 | return callback(err, null); |
| 248 | } | |
| 249 | ||
| 250 | // Set the cursor id | |
| 251 | 0 | cursorId = result.cursorId; |
| 252 | 0 | if(typeof cursorId == 'number') cursorId = Long.fromNumber(cursorId); |
| 253 | ||
| 254 | // Add the items | |
| 255 | 0 | items = items.concat(result.documents); |
| 256 | // If no more items | |
| 257 | 0 | if(items.length == 0) { |
| 258 | 0 | state = 'closed'; |
| 259 | 0 | return callback(null, null); |
| 260 | } | |
| 261 | ||
| 262 | // Return the item | |
| 263 | 0 | return callback(null, items.shift()); |
| 264 | }) | |
| 265 | } | |
| 266 | } | |
| 267 | ||
| 268 | // Validate if the cursor is closed | |
| 269 | 0 | this.isClosed = function() { |
| 270 | 0 | return state == 'closed'; |
| 271 | } | |
| 272 | ||
| 273 | // Allow us to set the MaxTimeMS | |
| 274 | 0 | this.maxTimeMS = function(_maxTimeMS) { |
| 275 | 0 | maxTimeMS = _maxTimeMS; |
| 276 | } | |
| 277 | ||
| 278 | // Close the cursor sending a kill cursor command if needed | |
| 279 | 0 | this.close = function(options, callback) { |
| 280 | 0 | if(typeof options == 'function') { |
| 281 | 0 | callback = options; |
| 282 | 0 | options = {}; |
| 283 | } | |
| 284 | ||
| 285 | // Close the cursor if not needed | |
| 286 | 0 | if(cursorId instanceof Long && cursorId.greaterThan(Long.fromInt(0))) { |
| 287 | 0 | try { |
| 288 | 0 | var command = new KillCursorCommand(this.db, [cursorId]); |
| 289 | // Added an empty callback to ensure we don't throw any null exceptions | |
| 290 | 0 | db._executeQueryCommand(command, {connection:connection}); |
| 291 | } catch(err) {} | |
| 292 | } | |
| 293 | ||
| 294 | // Null out the connection | |
| 295 | 0 | connection = null; |
| 296 | // Reset cursor id | |
| 297 | 0 | cursorId = Long.fromInt(0); |
| 298 | // Set to closed status | |
| 299 | 0 | state = 'closed'; |
| 300 | // Clear out all the items | |
| 301 | 0 | items = null; |
| 302 | ||
| 303 | 0 | if(callback) { |
| 304 | 0 | callback(null, null); |
| 305 | } | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 | 1 | exports.CommandCursor = CommandCursor; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | Base object used for common functionality | |
| 3 | **/ | |
| 4 | 1 | var BaseCommand = exports.BaseCommand = function BaseCommand() { |
| 5 | }; | |
| 6 | ||
| 7 | 1 | var id = 1; |
| 8 | 1 | BaseCommand.prototype.getRequestId = function getRequestId() { |
| 9 | 0 | if (!this.requestId) this.requestId = id++; |
| 10 | 0 | return this.requestId; |
| 11 | }; | |
| 12 | ||
| 13 | 1 | BaseCommand.prototype.setMongosReadPreference = function setMongosReadPreference(readPreference, tags) {} |
| 14 | ||
| 15 | 1 | BaseCommand.prototype.updateRequestId = function() { |
| 16 | 0 | this.requestId = id++; |
| 17 | 0 | return this.requestId; |
| 18 | }; | |
| 19 | ||
| 20 | // OpCodes | |
| 21 | 1 | BaseCommand.OP_REPLY = 1; |
| 22 | 1 | BaseCommand.OP_MSG = 1000; |
| 23 | 1 | BaseCommand.OP_UPDATE = 2001; |
| 24 | 1 | BaseCommand.OP_INSERT = 2002; |
| 25 | 1 | BaseCommand.OP_GET_BY_OID = 2003; |
| 26 | 1 | BaseCommand.OP_QUERY = 2004; |
| 27 | 1 | BaseCommand.OP_GET_MORE = 2005; |
| 28 | 1 | BaseCommand.OP_DELETE = 2006; |
| 29 | 1 | BaseCommand.OP_KILL_CURSORS = 2007; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var QueryCommand = require('./query_command').QueryCommand, |
| 2 | InsertCommand = require('./insert_command').InsertCommand, | |
| 3 | inherits = require('util').inherits, | |
| 4 | utils = require('../utils'), | |
| 5 | crypto = require('crypto'); | |
| 6 | ||
| 7 | /** | |
| 8 | Db Command | |
| 9 | **/ | |
| 10 | 1 | var DbCommand = exports.DbCommand = function(dbInstance, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { |
| 11 | 0 | QueryCommand.call(this); |
| 12 | 0 | this.collectionName = collectionName; |
| 13 | 0 | this.queryOptions = queryOptions; |
| 14 | 0 | this.numberToSkip = numberToSkip; |
| 15 | 0 | this.numberToReturn = numberToReturn; |
| 16 | 0 | this.query = query; |
| 17 | 0 | this.returnFieldSelector = returnFieldSelector; |
| 18 | 0 | this.db = dbInstance; |
| 19 | ||
| 20 | // Set the slave ok bit | |
| 21 | 0 | if(this.db && this.db.slaveOk) { |
| 22 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 23 | } | |
| 24 | ||
| 25 | // Make sure we don't get a null exception | |
| 26 | 0 | options = options == null ? {} : options; |
| 27 | ||
| 28 | // Allow for overriding the BSON checkKeys function | |
| 29 | 0 | this.checkKeys = typeof options['checkKeys'] == 'boolean' ? options["checkKeys"] : true; |
| 30 | ||
| 31 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 32 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 33 | 0 | this.serializeFunctions = true; |
| 34 | } | |
| 35 | }; | |
| 36 | ||
| 37 | 1 | inherits(DbCommand, QueryCommand); |
| 38 | ||
| 39 | // Constants | |
| 40 | 1 | DbCommand.SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"; |
| 41 | 1 | DbCommand.SYSTEM_INDEX_COLLECTION = "system.indexes"; |
| 42 | 1 | DbCommand.SYSTEM_PROFILE_COLLECTION = "system.profile"; |
| 43 | 1 | DbCommand.SYSTEM_USER_COLLECTION = "system.users"; |
| 44 | 1 | DbCommand.SYSTEM_COMMAND_COLLECTION = "$cmd"; |
| 45 | 1 | DbCommand.SYSTEM_JS_COLLECTION = "system.js"; |
| 46 | ||
| 47 | // New commands | |
| 48 | 1 | DbCommand.NcreateIsMasterCommand = function(db, databaseName) { |
| 49 | 0 | return new DbCommand(db, databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); |
| 50 | }; | |
| 51 | ||
| 52 | // Provide constructors for different db commands | |
| 53 | 1 | DbCommand.createIsMasterCommand = function(db) { |
| 54 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'ismaster':1}, null); |
| 55 | }; | |
| 56 | ||
| 57 | 1 | DbCommand.createCollectionInfoCommand = function(db, selector) { |
| 58 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_NAMESPACE_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, 0, selector, null); |
| 59 | }; | |
| 60 | ||
| 61 | 1 | DbCommand.createGetNonceCommand = function(db, options) { |
| 62 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getnonce':1}, null); |
| 63 | }; | |
| 64 | ||
| 65 | 1 | DbCommand.createAuthenticationCommand = function(db, username, password, nonce, authdb) { |
| 66 | // Use node md5 generator | |
| 67 | 0 | var md5 = crypto.createHash('md5'); |
| 68 | // Generate keys used for authentication | |
| 69 | 0 | md5.update(username + ":mongo:" + password); |
| 70 | 0 | var hash_password = md5.digest('hex'); |
| 71 | // Final key | |
| 72 | 0 | md5 = crypto.createHash('md5'); |
| 73 | 0 | md5.update(nonce + username + hash_password); |
| 74 | 0 | var key = md5.digest('hex'); |
| 75 | // Creat selector | |
| 76 | 0 | var selector = {'authenticate':1, 'user':username, 'nonce':nonce, 'key':key}; |
| 77 | // Create db command | |
| 78 | 0 | return new DbCommand(db, authdb + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NONE, 0, -1, selector, null); |
| 79 | }; | |
| 80 | ||
| 81 | 1 | DbCommand.createLogoutCommand = function(db) { |
| 82 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'logout':1}, null); |
| 83 | }; | |
| 84 | ||
| 85 | 1 | DbCommand.createCreateCollectionCommand = function(db, collectionName, options) { |
| 86 | 0 | var selector = {'create':collectionName}; |
| 87 | // Modify the options to ensure correct behaviour | |
| 88 | 0 | for(var name in options) { |
| 89 | 0 | if(options[name] != null && options[name].constructor != Function) selector[name] = options[name]; |
| 90 | } | |
| 91 | // Execute the command | |
| 92 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, selector, null); |
| 93 | }; | |
| 94 | ||
| 95 | 1 | DbCommand.createDropCollectionCommand = function(db, collectionName) { |
| 96 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'drop':collectionName}, null); |
| 97 | }; | |
| 98 | ||
| 99 | 1 | DbCommand.createRenameCollectionCommand = function(db, fromCollectionName, toCollectionName, options) { |
| 100 | 0 | var renameCollection = db.databaseName + "." + fromCollectionName; |
| 101 | 0 | var toCollection = db.databaseName + "." + toCollectionName; |
| 102 | 0 | var dropTarget = options && options.dropTarget ? options.dropTarget : false; |
| 103 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'renameCollection':renameCollection, 'to':toCollection, 'dropTarget':dropTarget}, null); |
| 104 | }; | |
| 105 | ||
| 106 | 1 | DbCommand.createGetLastErrorCommand = function(options, db) { |
| 107 | ||
| 108 | 0 | if (typeof db === 'undefined') { |
| 109 | 0 | db = options; |
| 110 | 0 | options = {}; |
| 111 | } | |
| 112 | // Final command | |
| 113 | 0 | var command = {'getlasterror':1}; |
| 114 | // If we have an options Object let's merge in the fields (fsync/wtimeout/w) | |
| 115 | 0 | if('object' === typeof options) { |
| 116 | 0 | for(var name in options) { |
| 117 | 0 | command[name] = options[name] |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | // Special case for w == 1, remove the w | |
| 122 | 0 | if(1 == command.w) { |
| 123 | 0 | delete command.w; |
| 124 | } | |
| 125 | ||
| 126 | // Execute command | |
| 127 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command, null); |
| 128 | }; | |
| 129 | ||
| 130 | 1 | DbCommand.createGetLastStatusCommand = DbCommand.createGetLastErrorCommand; |
| 131 | ||
| 132 | 1 | DbCommand.createGetPreviousErrorsCommand = function(db) { |
| 133 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'getpreverror':1}, null); |
| 134 | }; | |
| 135 | ||
| 136 | 1 | DbCommand.createResetErrorHistoryCommand = function(db) { |
| 137 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reseterror':1}, null); |
| 138 | }; | |
| 139 | ||
| 140 | 1 | DbCommand.createCreateIndexCommand = function(db, collectionName, fieldOrSpec, options) { |
| 141 | 0 | var fieldHash = {}; |
| 142 | 0 | var indexes = []; |
| 143 | 0 | var keys; |
| 144 | ||
| 145 | // Get all the fields accordingly | |
| 146 | 0 | if('string' == typeof fieldOrSpec) { |
| 147 | // 'type' | |
| 148 | 0 | indexes.push(fieldOrSpec + '_' + 1); |
| 149 | 0 | fieldHash[fieldOrSpec] = 1; |
| 150 | ||
| 151 | 0 | } else if(utils.isArray(fieldOrSpec)) { |
| 152 | ||
| 153 | 0 | fieldOrSpec.forEach(function(f) { |
| 154 | 0 | if('string' == typeof f) { |
| 155 | // [{location:'2d'}, 'type'] | |
| 156 | 0 | indexes.push(f + '_' + 1); |
| 157 | 0 | fieldHash[f] = 1; |
| 158 | 0 | } else if(utils.isArray(f)) { |
| 159 | // [['location', '2d'],['type', 1]] | |
| 160 | 0 | indexes.push(f[0] + '_' + (f[1] || 1)); |
| 161 | 0 | fieldHash[f[0]] = f[1] || 1; |
| 162 | 0 | } else if(utils.isObject(f)) { |
| 163 | // [{location:'2d'}, {type:1}] | |
| 164 | 0 | keys = Object.keys(f); |
| 165 | 0 | keys.forEach(function(k) { |
| 166 | 0 | indexes.push(k + '_' + f[k]); |
| 167 | 0 | fieldHash[k] = f[k]; |
| 168 | }); | |
| 169 | } else { | |
| 170 | // undefined (ignore) | |
| 171 | } | |
| 172 | }); | |
| 173 | ||
| 174 | 0 | } else if(utils.isObject(fieldOrSpec)) { |
| 175 | // {location:'2d', type:1} | |
| 176 | 0 | keys = Object.keys(fieldOrSpec); |
| 177 | 0 | keys.forEach(function(key) { |
| 178 | 0 | indexes.push(key + '_' + fieldOrSpec[key]); |
| 179 | 0 | fieldHash[key] = fieldOrSpec[key]; |
| 180 | }); | |
| 181 | } | |
| 182 | ||
| 183 | // Generate the index name | |
| 184 | 0 | var indexName = typeof options.name == 'string' |
| 185 | ? options.name | |
| 186 | : indexes.join("_"); | |
| 187 | ||
| 188 | 0 | var selector = { |
| 189 | 'ns': db.databaseName + "." + collectionName, | |
| 190 | 'key': fieldHash, | |
| 191 | 'name': indexName | |
| 192 | } | |
| 193 | ||
| 194 | // Ensure we have a correct finalUnique | |
| 195 | 0 | var finalUnique = options == null || 'object' === typeof options |
| 196 | ? false | |
| 197 | : options; | |
| 198 | ||
| 199 | // Set up options | |
| 200 | 0 | options = options == null || typeof options == 'boolean' |
| 201 | ? {} | |
| 202 | : options; | |
| 203 | ||
| 204 | // Add all the options | |
| 205 | 0 | var keys = Object.keys(options); |
| 206 | 0 | for(var i = 0; i < keys.length; i++) { |
| 207 | 0 | selector[keys[i]] = options[keys[i]]; |
| 208 | } | |
| 209 | ||
| 210 | 0 | if(selector['unique'] == null) |
| 211 | 0 | selector['unique'] = finalUnique; |
| 212 | ||
| 213 | 0 | var name = db.databaseName + "." + DbCommand.SYSTEM_INDEX_COLLECTION; |
| 214 | 0 | var cmd = new InsertCommand(db, name, false); |
| 215 | 0 | return cmd.add(selector); |
| 216 | }; | |
| 217 | ||
| 218 | 1 | DbCommand.logoutCommand = function(db, command_hash, options) { |
| 219 | 0 | var dbName = options != null && options['authdb'] != null ? options['authdb'] : db.databaseName; |
| 220 | 0 | return new DbCommand(db, dbName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); |
| 221 | } | |
| 222 | ||
| 223 | 1 | DbCommand.createDropIndexCommand = function(db, collectionName, indexName) { |
| 224 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'deleteIndexes':collectionName, 'index':indexName}, null); |
| 225 | }; | |
| 226 | ||
| 227 | 1 | DbCommand.createReIndexCommand = function(db, collectionName) { |
| 228 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'reIndex':collectionName}, null); |
| 229 | }; | |
| 230 | ||
| 231 | 1 | DbCommand.createDropDatabaseCommand = function(db) { |
| 232 | 0 | return new DbCommand(db, db.databaseName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, {'dropDatabase':1}, null); |
| 233 | }; | |
| 234 | ||
| 235 | 1 | DbCommand.createDbCommand = function(db, command_hash, options, auth_db) { |
| 236 | 0 | var db_name = (auth_db ? auth_db : db.databaseName) + "." + DbCommand.SYSTEM_COMMAND_COLLECTION; |
| 237 | 0 | options = options == null ? {checkKeys: false} : options; |
| 238 | 0 | return new DbCommand(db, db_name, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null, options); |
| 239 | }; | |
| 240 | ||
| 241 | 1 | DbCommand.createAdminDbCommand = function(db, command_hash) { |
| 242 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT, 0, -1, command_hash, null); |
| 243 | }; | |
| 244 | ||
| 245 | 1 | DbCommand.createAdminDbCommandSlaveOk = function(db, command_hash) { |
| 246 | 0 | return new DbCommand(db, "admin." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null); |
| 247 | }; | |
| 248 | ||
| 249 | 1 | DbCommand.createDbSlaveOkCommand = function(db, command_hash, options) { |
| 250 | 0 | options = options == null ? {checkKeys: false} : options; |
| 251 | 0 | var dbName = options.dbName ? options.dbName : db.databaseName; |
| 252 | 0 | return new DbCommand(db, dbName + "." + DbCommand.SYSTEM_COMMAND_COLLECTION, QueryCommand.OPTS_NO_CURSOR_TIMEOUT | QueryCommand.OPTS_SLAVE, 0, -1, command_hash, null, options); |
| 253 | }; | |
| 254 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var DeleteCommand = exports.DeleteCommand = function(db, collectionName, selector, flags) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | // Validate correctness off the selector | |
| 11 | 0 | var object = selector; |
| 12 | 0 | if(Buffer.isBuffer(object)) { |
| 13 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 14 | 0 | if(object_size != object.length) { |
| 15 | 0 | var error = new Error("delete raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 16 | 0 | error.name = 'MongoError'; |
| 17 | 0 | throw error; |
| 18 | } | |
| 19 | } | |
| 20 | ||
| 21 | 0 | this.flags = flags; |
| 22 | 0 | this.collectionName = collectionName; |
| 23 | 0 | this.selector = selector; |
| 24 | 0 | this.db = db; |
| 25 | }; | |
| 26 | ||
| 27 | 1 | inherits(DeleteCommand, BaseCommand); |
| 28 | ||
| 29 | 1 | DeleteCommand.OP_DELETE = 2006; |
| 30 | ||
| 31 | /* | |
| 32 | struct { | |
| 33 | MsgHeader header; // standard message header | |
| 34 | int32 ZERO; // 0 - reserved for future use | |
| 35 | cstring fullCollectionName; // "dbname.collectionname" | |
| 36 | int32 ZERO; // 0 - reserved for future use | |
| 37 | mongo.BSON selector; // query object. See below for details. | |
| 38 | } | |
| 39 | */ | |
| 40 | 1 | DeleteCommand.prototype.toBinary = function(bsonSettings) { |
| 41 | // Validate that we are not passing 0x00 in the colletion name | |
| 42 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 43 | 0 | throw new Error("namespace cannot contain a null character"); |
| 44 | } | |
| 45 | ||
| 46 | // Calculate total length of the document | |
| 47 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.selector, false, true) + (4 * 4); |
| 48 | ||
| 49 | // Enforce maximum bson size | |
| 50 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 51 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 52 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 53 | ||
| 54 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 55 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 56 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 57 | ||
| 58 | // Let's build the single pass buffer command | |
| 59 | 0 | var _index = 0; |
| 60 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 61 | // Write the header information to the buffer | |
| 62 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 63 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 64 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 65 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 66 | // Adjust index | |
| 67 | 0 | _index = _index + 4; |
| 68 | // Write the request ID | |
| 69 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 70 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 71 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 72 | 0 | _command[_index] = this.requestId & 0xff; |
| 73 | // Adjust index | |
| 74 | 0 | _index = _index + 4; |
| 75 | // Write zero | |
| 76 | 0 | _command[_index++] = 0; |
| 77 | 0 | _command[_index++] = 0; |
| 78 | 0 | _command[_index++] = 0; |
| 79 | 0 | _command[_index++] = 0; |
| 80 | // Write the op_code for the command | |
| 81 | 0 | _command[_index + 3] = (DeleteCommand.OP_DELETE >> 24) & 0xff; |
| 82 | 0 | _command[_index + 2] = (DeleteCommand.OP_DELETE >> 16) & 0xff; |
| 83 | 0 | _command[_index + 1] = (DeleteCommand.OP_DELETE >> 8) & 0xff; |
| 84 | 0 | _command[_index] = DeleteCommand.OP_DELETE & 0xff; |
| 85 | // Adjust index | |
| 86 | 0 | _index = _index + 4; |
| 87 | ||
| 88 | // Write zero | |
| 89 | 0 | _command[_index++] = 0; |
| 90 | 0 | _command[_index++] = 0; |
| 91 | 0 | _command[_index++] = 0; |
| 92 | 0 | _command[_index++] = 0; |
| 93 | ||
| 94 | // Write the collection name to the command | |
| 95 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 96 | 0 | _command[_index - 1] = 0; |
| 97 | ||
| 98 | // Write the flags | |
| 99 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 100 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 101 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 102 | 0 | _command[_index] = this.flags & 0xff; |
| 103 | // Adjust index | |
| 104 | 0 | _index = _index + 4; |
| 105 | ||
| 106 | // Document binary length | |
| 107 | 0 | var documentLength = 0 |
| 108 | ||
| 109 | // Serialize the selector | |
| 110 | // If we are passing a raw buffer, do minimal validation | |
| 111 | 0 | if(Buffer.isBuffer(this.selector)) { |
| 112 | 0 | documentLength = this.selector.length; |
| 113 | // Copy the data into the current buffer | |
| 114 | 0 | this.selector.copy(_command, _index); |
| 115 | } else { | |
| 116 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(this.selector, false, _command, _index) - _index + 1; |
| 117 | } | |
| 118 | ||
| 119 | // Write the length to the document | |
| 120 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 121 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 122 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 123 | 0 | _command[_index] = documentLength & 0xff; |
| 124 | // Update index in buffer | |
| 125 | 0 | _index = _index + documentLength; |
| 126 | // Add terminating 0 for the object | |
| 127 | 0 | _command[_index - 1] = 0; |
| 128 | 0 | return _command; |
| 129 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits, | |
| 3 | binaryutils = require('../utils'); | |
| 4 | ||
| 5 | /** | |
| 6 | Get More Document Command | |
| 7 | **/ | |
| 8 | 1 | var GetMoreCommand = exports.GetMoreCommand = function(db, collectionName, numberToReturn, cursorId) { |
| 9 | 0 | BaseCommand.call(this); |
| 10 | ||
| 11 | 0 | this.collectionName = collectionName; |
| 12 | 0 | this.numberToReturn = numberToReturn; |
| 13 | 0 | this.cursorId = cursorId; |
| 14 | 0 | this.db = db; |
| 15 | }; | |
| 16 | ||
| 17 | 1 | inherits(GetMoreCommand, BaseCommand); |
| 18 | ||
| 19 | 1 | GetMoreCommand.OP_GET_MORE = 2005; |
| 20 | ||
| 21 | 1 | GetMoreCommand.prototype.toBinary = function() { |
| 22 | // Validate that we are not passing 0x00 in the colletion name | |
| 23 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 24 | 0 | throw new Error("namespace cannot contain a null character"); |
| 25 | } | |
| 26 | ||
| 27 | // Calculate total length of the document | |
| 28 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 8 + (4 * 4); |
| 29 | // Let's build the single pass buffer command | |
| 30 | 0 | var _index = 0; |
| 31 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 32 | // Write the header information to the buffer | |
| 33 | 0 | _command[_index++] = totalLengthOfCommand & 0xff; |
| 34 | 0 | _command[_index++] = (totalLengthOfCommand >> 8) & 0xff; |
| 35 | 0 | _command[_index++] = (totalLengthOfCommand >> 16) & 0xff; |
| 36 | 0 | _command[_index++] = (totalLengthOfCommand >> 24) & 0xff; |
| 37 | ||
| 38 | // Write the request ID | |
| 39 | 0 | _command[_index++] = this.requestId & 0xff; |
| 40 | 0 | _command[_index++] = (this.requestId >> 8) & 0xff; |
| 41 | 0 | _command[_index++] = (this.requestId >> 16) & 0xff; |
| 42 | 0 | _command[_index++] = (this.requestId >> 24) & 0xff; |
| 43 | ||
| 44 | // Write zero | |
| 45 | 0 | _command[_index++] = 0; |
| 46 | 0 | _command[_index++] = 0; |
| 47 | 0 | _command[_index++] = 0; |
| 48 | 0 | _command[_index++] = 0; |
| 49 | ||
| 50 | // Write the op_code for the command | |
| 51 | 0 | _command[_index++] = GetMoreCommand.OP_GET_MORE & 0xff; |
| 52 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 8) & 0xff; |
| 53 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 16) & 0xff; |
| 54 | 0 | _command[_index++] = (GetMoreCommand.OP_GET_MORE >> 24) & 0xff; |
| 55 | ||
| 56 | // Write zero | |
| 57 | 0 | _command[_index++] = 0; |
| 58 | 0 | _command[_index++] = 0; |
| 59 | 0 | _command[_index++] = 0; |
| 60 | 0 | _command[_index++] = 0; |
| 61 | ||
| 62 | // Write the collection name to the command | |
| 63 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 64 | 0 | _command[_index - 1] = 0; |
| 65 | ||
| 66 | // Number of documents to return | |
| 67 | 0 | _command[_index++] = this.numberToReturn & 0xff; |
| 68 | 0 | _command[_index++] = (this.numberToReturn >> 8) & 0xff; |
| 69 | 0 | _command[_index++] = (this.numberToReturn >> 16) & 0xff; |
| 70 | 0 | _command[_index++] = (this.numberToReturn >> 24) & 0xff; |
| 71 | ||
| 72 | // Encode the cursor id | |
| 73 | 0 | var low_bits = this.cursorId.getLowBits(); |
| 74 | // Encode low bits | |
| 75 | 0 | _command[_index++] = low_bits & 0xff; |
| 76 | 0 | _command[_index++] = (low_bits >> 8) & 0xff; |
| 77 | 0 | _command[_index++] = (low_bits >> 16) & 0xff; |
| 78 | 0 | _command[_index++] = (low_bits >> 24) & 0xff; |
| 79 | ||
| 80 | 0 | var high_bits = this.cursorId.getHighBits(); |
| 81 | // Encode high bits | |
| 82 | 0 | _command[_index++] = high_bits & 0xff; |
| 83 | 0 | _command[_index++] = (high_bits >> 8) & 0xff; |
| 84 | 0 | _command[_index++] = (high_bits >> 16) & 0xff; |
| 85 | 0 | _command[_index++] = (high_bits >> 24) & 0xff; |
| 86 | // Return command | |
| 87 | 0 | return _command; |
| 88 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var InsertCommand = exports.InsertCommand = function(db, collectionName, checkKeys, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | 0 | this.collectionName = collectionName; |
| 11 | 0 | this.documents = []; |
| 12 | 0 | this.checkKeys = checkKeys == null ? true : checkKeys; |
| 13 | 0 | this.db = db; |
| 14 | 0 | this.flags = 0; |
| 15 | 0 | this.serializeFunctions = false; |
| 16 | ||
| 17 | // Ensure valid options hash | |
| 18 | 0 | options = options == null ? {} : options; |
| 19 | ||
| 20 | // Check if we have keepGoing set -> set flag if it's the case | |
| 21 | 0 | if(options['keepGoing'] != null && options['keepGoing']) { |
| 22 | // This will finish inserting all non-index violating documents even if it returns an error | |
| 23 | 0 | this.flags = 1; |
| 24 | } | |
| 25 | ||
| 26 | // Check if we have keepGoing set -> set flag if it's the case | |
| 27 | 0 | if(options['continueOnError'] != null && options['continueOnError']) { |
| 28 | // This will finish inserting all non-index violating documents even if it returns an error | |
| 29 | 0 | this.flags = 1; |
| 30 | } | |
| 31 | ||
| 32 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 33 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 34 | 0 | this.serializeFunctions = true; |
| 35 | } | |
| 36 | }; | |
| 37 | ||
| 38 | 1 | inherits(InsertCommand, BaseCommand); |
| 39 | ||
| 40 | // OpCodes | |
| 41 | 1 | InsertCommand.OP_INSERT = 2002; |
| 42 | ||
| 43 | 1 | InsertCommand.prototype.add = function(document) { |
| 44 | 0 | if(Buffer.isBuffer(document)) { |
| 45 | 0 | var object_size = document[0] | document[1] << 8 | document[2] << 16 | document[3] << 24; |
| 46 | 0 | if(object_size != document.length) { |
| 47 | 0 | var error = new Error("insert raw message size does not match message header size [" + document.length + "] != [" + object_size + "]"); |
| 48 | 0 | error.name = 'MongoError'; |
| 49 | 0 | throw error; |
| 50 | } | |
| 51 | } | |
| 52 | ||
| 53 | 0 | this.documents.push(document); |
| 54 | 0 | return this; |
| 55 | }; | |
| 56 | ||
| 57 | /* | |
| 58 | struct { | |
| 59 | MsgHeader header; // standard message header | |
| 60 | int32 ZERO; // 0 - reserved for future use | |
| 61 | cstring fullCollectionName; // "dbname.collectionname" | |
| 62 | BSON[] documents; // one or more documents to insert into the collection | |
| 63 | } | |
| 64 | */ | |
| 65 | 1 | InsertCommand.prototype.toBinary = function(bsonSettings) { |
| 66 | // Validate that we are not passing 0x00 in the colletion name | |
| 67 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 68 | 0 | throw new Error("namespace cannot contain a null character"); |
| 69 | } | |
| 70 | ||
| 71 | // Calculate total length of the document | |
| 72 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + (4 * 4); |
| 73 | // var docLength = 0 | |
| 74 | 0 | for(var i = 0; i < this.documents.length; i++) { |
| 75 | 0 | if(Buffer.isBuffer(this.documents[i])) { |
| 76 | 0 | totalLengthOfCommand += this.documents[i].length; |
| 77 | } else { | |
| 78 | // Calculate size of document | |
| 79 | 0 | totalLengthOfCommand += this.db.bson.calculateObjectSize(this.documents[i], this.serializeFunctions, true); |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | // Enforce maximum bson size | |
| 84 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 85 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 86 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 87 | ||
| 88 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 89 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 90 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 91 | ||
| 92 | // Let's build the single pass buffer command | |
| 93 | 0 | var _index = 0; |
| 94 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 95 | // Write the header information to the buffer | |
| 96 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 97 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 98 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 99 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 100 | // Adjust index | |
| 101 | 0 | _index = _index + 4; |
| 102 | // Write the request ID | |
| 103 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 104 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 105 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 106 | 0 | _command[_index] = this.requestId & 0xff; |
| 107 | // Adjust index | |
| 108 | 0 | _index = _index + 4; |
| 109 | // Write zero | |
| 110 | 0 | _command[_index++] = 0; |
| 111 | 0 | _command[_index++] = 0; |
| 112 | 0 | _command[_index++] = 0; |
| 113 | 0 | _command[_index++] = 0; |
| 114 | // Write the op_code for the command | |
| 115 | 0 | _command[_index + 3] = (InsertCommand.OP_INSERT >> 24) & 0xff; |
| 116 | 0 | _command[_index + 2] = (InsertCommand.OP_INSERT >> 16) & 0xff; |
| 117 | 0 | _command[_index + 1] = (InsertCommand.OP_INSERT >> 8) & 0xff; |
| 118 | 0 | _command[_index] = InsertCommand.OP_INSERT & 0xff; |
| 119 | // Adjust index | |
| 120 | 0 | _index = _index + 4; |
| 121 | // Write flags if any | |
| 122 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 123 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 124 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 125 | 0 | _command[_index] = this.flags & 0xff; |
| 126 | // Adjust index | |
| 127 | 0 | _index = _index + 4; |
| 128 | // Write the collection name to the command | |
| 129 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 130 | 0 | _command[_index - 1] = 0; |
| 131 | ||
| 132 | // Write all the bson documents to the buffer at the index offset | |
| 133 | 0 | for(var i = 0; i < this.documents.length; i++) { |
| 134 | // Document binary length | |
| 135 | 0 | var documentLength = 0 |
| 136 | 0 | var object = this.documents[i]; |
| 137 | ||
| 138 | // Serialize the selector | |
| 139 | // If we are passing a raw buffer, do minimal validation | |
| 140 | 0 | if(Buffer.isBuffer(object)) { |
| 141 | 0 | documentLength = object.length; |
| 142 | // Copy the data into the current buffer | |
| 143 | 0 | object.copy(_command, _index); |
| 144 | } else { | |
| 145 | // Serialize the document straight to the buffer | |
| 146 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 147 | } | |
| 148 | ||
| 149 | // Write the length to the document | |
| 150 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 151 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 152 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 153 | 0 | _command[_index] = documentLength & 0xff; |
| 154 | // Update index in buffer | |
| 155 | 0 | _index = _index + documentLength; |
| 156 | // Add terminating 0 for the object | |
| 157 | 0 | _command[_index - 1] = 0; |
| 158 | } | |
| 159 | ||
| 160 | 0 | return _command; |
| 161 | }; | |
| 162 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits, | |
| 3 | binaryutils = require('../utils'); | |
| 4 | ||
| 5 | /** | |
| 6 | Insert Document Command | |
| 7 | **/ | |
| 8 | 1 | var KillCursorCommand = exports.KillCursorCommand = function(db, cursorIds) { |
| 9 | 0 | BaseCommand.call(this); |
| 10 | ||
| 11 | 0 | this.cursorIds = cursorIds; |
| 12 | 0 | this.db = db; |
| 13 | }; | |
| 14 | ||
| 15 | 1 | inherits(KillCursorCommand, BaseCommand); |
| 16 | ||
| 17 | 1 | KillCursorCommand.OP_KILL_CURSORS = 2007; |
| 18 | ||
| 19 | /* | |
| 20 | struct { | |
| 21 | MsgHeader header; // standard message header | |
| 22 | int32 ZERO; // 0 - reserved for future use | |
| 23 | int32 numberOfCursorIDs; // number of cursorIDs in message | |
| 24 | int64[] cursorIDs; // array of cursorIDs to close | |
| 25 | } | |
| 26 | */ | |
| 27 | 1 | KillCursorCommand.prototype.toBinary = function() { |
| 28 | // Calculate total length of the document | |
| 29 | 0 | var totalLengthOfCommand = 4 + 4 + (4 * 4) + (this.cursorIds.length * 8); |
| 30 | // Let's build the single pass buffer command | |
| 31 | 0 | var _index = 0; |
| 32 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 33 | // Write the header information to the buffer | |
| 34 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 35 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 36 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 37 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 38 | // Adjust index | |
| 39 | 0 | _index = _index + 4; |
| 40 | // Write the request ID | |
| 41 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 42 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 43 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 44 | 0 | _command[_index] = this.requestId & 0xff; |
| 45 | // Adjust index | |
| 46 | 0 | _index = _index + 4; |
| 47 | // Write zero | |
| 48 | 0 | _command[_index++] = 0; |
| 49 | 0 | _command[_index++] = 0; |
| 50 | 0 | _command[_index++] = 0; |
| 51 | 0 | _command[_index++] = 0; |
| 52 | // Write the op_code for the command | |
| 53 | 0 | _command[_index + 3] = (KillCursorCommand.OP_KILL_CURSORS >> 24) & 0xff; |
| 54 | 0 | _command[_index + 2] = (KillCursorCommand.OP_KILL_CURSORS >> 16) & 0xff; |
| 55 | 0 | _command[_index + 1] = (KillCursorCommand.OP_KILL_CURSORS >> 8) & 0xff; |
| 56 | 0 | _command[_index] = KillCursorCommand.OP_KILL_CURSORS & 0xff; |
| 57 | // Adjust index | |
| 58 | 0 | _index = _index + 4; |
| 59 | ||
| 60 | // Write zero | |
| 61 | 0 | _command[_index++] = 0; |
| 62 | 0 | _command[_index++] = 0; |
| 63 | 0 | _command[_index++] = 0; |
| 64 | 0 | _command[_index++] = 0; |
| 65 | ||
| 66 | // Number of cursors to kill | |
| 67 | 0 | var numberOfCursors = this.cursorIds.length; |
| 68 | 0 | _command[_index + 3] = (numberOfCursors >> 24) & 0xff; |
| 69 | 0 | _command[_index + 2] = (numberOfCursors >> 16) & 0xff; |
| 70 | 0 | _command[_index + 1] = (numberOfCursors >> 8) & 0xff; |
| 71 | 0 | _command[_index] = numberOfCursors & 0xff; |
| 72 | // Adjust index | |
| 73 | 0 | _index = _index + 4; |
| 74 | ||
| 75 | // Encode all the cursors | |
| 76 | 0 | for(var i = 0; i < this.cursorIds.length; i++) { |
| 77 | // Encode the cursor id | |
| 78 | 0 | var low_bits = this.cursorIds[i].getLowBits(); |
| 79 | // Encode low bits | |
| 80 | 0 | _command[_index + 3] = (low_bits >> 24) & 0xff; |
| 81 | 0 | _command[_index + 2] = (low_bits >> 16) & 0xff; |
| 82 | 0 | _command[_index + 1] = (low_bits >> 8) & 0xff; |
| 83 | 0 | _command[_index] = low_bits & 0xff; |
| 84 | // Adjust index | |
| 85 | 0 | _index = _index + 4; |
| 86 | ||
| 87 | 0 | var high_bits = this.cursorIds[i].getHighBits(); |
| 88 | // Encode high bits | |
| 89 | 0 | _command[_index + 3] = (high_bits >> 24) & 0xff; |
| 90 | 0 | _command[_index + 2] = (high_bits >> 16) & 0xff; |
| 91 | 0 | _command[_index + 1] = (high_bits >> 8) & 0xff; |
| 92 | 0 | _command[_index] = high_bits & 0xff; |
| 93 | // Adjust index | |
| 94 | 0 | _index = _index + 4; |
| 95 | } | |
| 96 | ||
| 97 | 0 | return _command; |
| 98 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Insert Document Command | |
| 6 | **/ | |
| 7 | 1 | var QueryCommand = exports.QueryCommand = function(db, collectionName, queryOptions, numberToSkip, numberToReturn, query, returnFieldSelector, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | // Validate correctness off the selector | |
| 11 | 0 | var object = query, |
| 12 | object_size; | |
| 13 | 0 | if(Buffer.isBuffer(object)) { |
| 14 | 0 | object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 15 | 0 | if(object_size != object.length) { |
| 16 | 0 | var error = new Error("query selector raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 17 | 0 | error.name = 'MongoError'; |
| 18 | 0 | throw error; |
| 19 | } | |
| 20 | } | |
| 21 | ||
| 22 | 0 | object = returnFieldSelector; |
| 23 | 0 | if(Buffer.isBuffer(object)) { |
| 24 | 0 | object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 25 | 0 | if(object_size != object.length) { |
| 26 | 0 | var error = new Error("query fields raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 27 | 0 | error.name = 'MongoError'; |
| 28 | 0 | throw error; |
| 29 | } | |
| 30 | } | |
| 31 | ||
| 32 | // Make sure we don't get a null exception | |
| 33 | 0 | options = options == null ? {} : options; |
| 34 | // Set up options | |
| 35 | 0 | this.collectionName = collectionName; |
| 36 | 0 | this.queryOptions = queryOptions; |
| 37 | 0 | this.numberToSkip = numberToSkip; |
| 38 | 0 | this.numberToReturn = numberToReturn; |
| 39 | ||
| 40 | // Ensure we have no null query | |
| 41 | 0 | query = query == null ? {} : query; |
| 42 | // Wrap query in the $query parameter so we can add read preferences for mongos | |
| 43 | 0 | this.query = query; |
| 44 | 0 | this.returnFieldSelector = returnFieldSelector; |
| 45 | 0 | this.db = db; |
| 46 | ||
| 47 | // Force the slave ok flag to be set if we are not using primary read preference | |
| 48 | 0 | if(this.db && this.db.slaveOk) { |
| 49 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 50 | } | |
| 51 | ||
| 52 | // If checkKeys set | |
| 53 | 0 | this.checkKeys = typeof options.checkKeys == 'boolean' ? options.checkKeys : false; |
| 54 | ||
| 55 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 56 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 57 | 0 | this.serializeFunctions = true; |
| 58 | } | |
| 59 | }; | |
| 60 | ||
| 61 | 1 | inherits(QueryCommand, BaseCommand); |
| 62 | ||
| 63 | 1 | QueryCommand.OP_QUERY = 2004; |
| 64 | ||
| 65 | /* | |
| 66 | * Adds the read prefrence to the current command | |
| 67 | */ | |
| 68 | 1 | QueryCommand.prototype.setMongosReadPreference = function(readPreference, tags) { |
| 69 | // If we have readPreference set to true set to secondary prefered | |
| 70 | 0 | if(readPreference == true) { |
| 71 | 0 | readPreference = 'secondaryPreferred'; |
| 72 | 0 | } else if(readPreference == 'false') { |
| 73 | 0 | readPreference = 'primary'; |
| 74 | } | |
| 75 | ||
| 76 | // Force the slave ok flag to be set if we are not using primary read preference | |
| 77 | 0 | if(readPreference != false && readPreference != 'primary') { |
| 78 | 0 | this.queryOptions |= QueryCommand.OPTS_SLAVE; |
| 79 | } | |
| 80 | ||
| 81 | // Backward compatibility, ensure $query only set on read preference so 1.8.X works | |
| 82 | 0 | if((readPreference != null || tags != null) && this.query['$query'] == null) { |
| 83 | 0 | this.query = {'$query': this.query}; |
| 84 | } | |
| 85 | ||
| 86 | // If we have no readPreference set and no tags, check if the slaveOk bit is set | |
| 87 | 0 | if(readPreference == null && tags == null) { |
| 88 | // If we have a slaveOk bit set the read preference for MongoS | |
| 89 | 0 | if(this.queryOptions & QueryCommand.OPTS_SLAVE) { |
| 90 | 0 | this.query['$readPreference'] = {mode: 'secondary'} |
| 91 | } else { | |
| 92 | 0 | this.query['$readPreference'] = {mode: 'primary'} |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | // Build read preference object | |
| 97 | 0 | if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') { |
| 98 | 0 | this.query['$readPreference'] = readPreference.toObject(); |
| 99 | 0 | } else if(readPreference != null) { |
| 100 | // Add the read preference | |
| 101 | 0 | this.query['$readPreference'] = {mode: readPreference}; |
| 102 | ||
| 103 | // If we have tags let's add them | |
| 104 | 0 | if(tags != null) { |
| 105 | 0 | this.query['$readPreference']['tags'] = tags; |
| 106 | } | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | /* | |
| 111 | struct { | |
| 112 | MsgHeader header; // standard message header | |
| 113 | int32 opts; // query options. See below for details. | |
| 114 | cstring fullCollectionName; // "dbname.collectionname" | |
| 115 | int32 numberToSkip; // number of documents to skip when returning results | |
| 116 | int32 numberToReturn; // number of documents to return in the first OP_REPLY | |
| 117 | BSON query ; // query object. See below for details. | |
| 118 | [ BSON returnFieldSelector; ] // OPTIONAL : selector indicating the fields to return. See below for details. | |
| 119 | } | |
| 120 | */ | |
| 121 | 1 | QueryCommand.prototype.toBinary = function(bsonSettings) { |
| 122 | // Validate that we are not passing 0x00 in the colletion name | |
| 123 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 124 | 0 | throw new Error("namespace cannot contain a null character"); |
| 125 | } | |
| 126 | ||
| 127 | // Total length of the command | |
| 128 | 0 | var totalLengthOfCommand = 0; |
| 129 | // Calculate total length of the document | |
| 130 | 0 | if(Buffer.isBuffer(this.query)) { |
| 131 | 0 | totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.query.length + (4 * 4); |
| 132 | } else { | |
| 133 | 0 | totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + 4 + this.db.bson.calculateObjectSize(this.query, this.serializeFunctions, true) + (4 * 4); |
| 134 | } | |
| 135 | ||
| 136 | // Calculate extra fields size | |
| 137 | 0 | if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { |
| 138 | 0 | if(Object.keys(this.returnFieldSelector).length > 0) { |
| 139 | 0 | totalLengthOfCommand += this.db.bson.calculateObjectSize(this.returnFieldSelector, this.serializeFunctions, true); |
| 140 | } | |
| 141 | 0 | } else if(Buffer.isBuffer(this.returnFieldSelector)) { |
| 142 | 0 | totalLengthOfCommand += this.returnFieldSelector.length; |
| 143 | } | |
| 144 | ||
| 145 | // Enforce maximum bson size | |
| 146 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 147 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 148 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 149 | ||
| 150 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 151 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 152 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 153 | ||
| 154 | // Let's build the single pass buffer command | |
| 155 | 0 | var _index = 0; |
| 156 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 157 | // Write the header information to the buffer | |
| 158 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 159 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 160 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 161 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 162 | // Adjust index | |
| 163 | 0 | _index = _index + 4; |
| 164 | // Write the request ID | |
| 165 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 166 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 167 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 168 | 0 | _command[_index] = this.requestId & 0xff; |
| 169 | // Adjust index | |
| 170 | 0 | _index = _index + 4; |
| 171 | // Write zero | |
| 172 | 0 | _command[_index++] = 0; |
| 173 | 0 | _command[_index++] = 0; |
| 174 | 0 | _command[_index++] = 0; |
| 175 | 0 | _command[_index++] = 0; |
| 176 | // Write the op_code for the command | |
| 177 | 0 | _command[_index + 3] = (QueryCommand.OP_QUERY >> 24) & 0xff; |
| 178 | 0 | _command[_index + 2] = (QueryCommand.OP_QUERY >> 16) & 0xff; |
| 179 | 0 | _command[_index + 1] = (QueryCommand.OP_QUERY >> 8) & 0xff; |
| 180 | 0 | _command[_index] = QueryCommand.OP_QUERY & 0xff; |
| 181 | // Adjust index | |
| 182 | 0 | _index = _index + 4; |
| 183 | ||
| 184 | // Write the query options | |
| 185 | 0 | _command[_index + 3] = (this.queryOptions >> 24) & 0xff; |
| 186 | 0 | _command[_index + 2] = (this.queryOptions >> 16) & 0xff; |
| 187 | 0 | _command[_index + 1] = (this.queryOptions >> 8) & 0xff; |
| 188 | 0 | _command[_index] = this.queryOptions & 0xff; |
| 189 | // Adjust index | |
| 190 | 0 | _index = _index + 4; |
| 191 | ||
| 192 | // Write the collection name to the command | |
| 193 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 194 | 0 | _command[_index - 1] = 0; |
| 195 | ||
| 196 | // Write the number of documents to skip | |
| 197 | 0 | _command[_index + 3] = (this.numberToSkip >> 24) & 0xff; |
| 198 | 0 | _command[_index + 2] = (this.numberToSkip >> 16) & 0xff; |
| 199 | 0 | _command[_index + 1] = (this.numberToSkip >> 8) & 0xff; |
| 200 | 0 | _command[_index] = this.numberToSkip & 0xff; |
| 201 | // Adjust index | |
| 202 | 0 | _index = _index + 4; |
| 203 | ||
| 204 | // Write the number of documents to return | |
| 205 | 0 | _command[_index + 3] = (this.numberToReturn >> 24) & 0xff; |
| 206 | 0 | _command[_index + 2] = (this.numberToReturn >> 16) & 0xff; |
| 207 | 0 | _command[_index + 1] = (this.numberToReturn >> 8) & 0xff; |
| 208 | 0 | _command[_index] = this.numberToReturn & 0xff; |
| 209 | // Adjust index | |
| 210 | 0 | _index = _index + 4; |
| 211 | ||
| 212 | // Document binary length | |
| 213 | 0 | var documentLength = 0 |
| 214 | 0 | var object = this.query; |
| 215 | ||
| 216 | // Serialize the selector | |
| 217 | 0 | if(Buffer.isBuffer(object)) { |
| 218 | 0 | documentLength = object.length; |
| 219 | // Copy the data into the current buffer | |
| 220 | 0 | object.copy(_command, _index); |
| 221 | } else { | |
| 222 | // Serialize the document straight to the buffer | |
| 223 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 224 | } | |
| 225 | ||
| 226 | // Write the length to the document | |
| 227 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 228 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 229 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 230 | 0 | _command[_index] = documentLength & 0xff; |
| 231 | // Update index in buffer | |
| 232 | 0 | _index = _index + documentLength; |
| 233 | // Add terminating 0 for the object | |
| 234 | 0 | _command[_index - 1] = 0; |
| 235 | ||
| 236 | // Push field selector if available | |
| 237 | 0 | if(this.returnFieldSelector != null && !(Buffer.isBuffer(this.returnFieldSelector))) { |
| 238 | 0 | if(Object.keys(this.returnFieldSelector).length > 0) { |
| 239 | 0 | var documentLength = this.db.bson.serializeWithBufferAndIndex(this.returnFieldSelector, this.checkKeys, _command, _index, this.serializeFunctions) - _index + 1; |
| 240 | // Write the length to the document | |
| 241 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 242 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 243 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 244 | 0 | _command[_index] = documentLength & 0xff; |
| 245 | // Update index in buffer | |
| 246 | 0 | _index = _index + documentLength; |
| 247 | // Add terminating 0 for the object | |
| 248 | 0 | _command[_index - 1] = 0; |
| 249 | } | |
| 250 | 0 | } if(this.returnFieldSelector != null && Buffer.isBuffer(this.returnFieldSelector)) { |
| 251 | // Document binary length | |
| 252 | 0 | var documentLength = 0 |
| 253 | 0 | var object = this.returnFieldSelector; |
| 254 | ||
| 255 | // Serialize the selector | |
| 256 | 0 | documentLength = object.length; |
| 257 | // Copy the data into the current buffer | |
| 258 | 0 | object.copy(_command, _index); |
| 259 | ||
| 260 | // Write the length to the document | |
| 261 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 262 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 263 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 264 | 0 | _command[_index] = documentLength & 0xff; |
| 265 | // Update index in buffer | |
| 266 | 0 | _index = _index + documentLength; |
| 267 | // Add terminating 0 for the object | |
| 268 | 0 | _command[_index - 1] = 0; |
| 269 | } | |
| 270 | ||
| 271 | // Return finished command | |
| 272 | 0 | return _command; |
| 273 | }; | |
| 274 | ||
| 275 | // Constants | |
| 276 | 1 | QueryCommand.OPTS_NONE = 0; |
| 277 | 1 | QueryCommand.OPTS_TAILABLE_CURSOR = 2; |
| 278 | 1 | QueryCommand.OPTS_SLAVE = 4; |
| 279 | 1 | QueryCommand.OPTS_OPLOG_REPLAY = 8; |
| 280 | 1 | QueryCommand.OPTS_NO_CURSOR_TIMEOUT = 16; |
| 281 | 1 | QueryCommand.OPTS_AWAIT_DATA = 32; |
| 282 | 1 | QueryCommand.OPTS_EXHAUST = 64; |
| 283 | 1 | QueryCommand.OPTS_PARTIAL = 128; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var BaseCommand = require('./base_command').BaseCommand, |
| 2 | inherits = require('util').inherits; | |
| 3 | ||
| 4 | /** | |
| 5 | Update Document Command | |
| 6 | **/ | |
| 7 | 1 | var UpdateCommand = exports.UpdateCommand = function(db, collectionName, spec, document, options) { |
| 8 | 0 | BaseCommand.call(this); |
| 9 | ||
| 10 | 0 | var object = spec; |
| 11 | 0 | if(Buffer.isBuffer(object)) { |
| 12 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 13 | 0 | if(object_size != object.length) { |
| 14 | 0 | var error = new Error("update spec raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 15 | 0 | error.name = 'MongoError'; |
| 16 | 0 | throw error; |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | 0 | var object = document; |
| 21 | 0 | if(Buffer.isBuffer(object)) { |
| 22 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 23 | 0 | if(object_size != object.length) { |
| 24 | 0 | var error = new Error("update document raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 25 | 0 | error.name = 'MongoError'; |
| 26 | 0 | throw error; |
| 27 | } | |
| 28 | } | |
| 29 | ||
| 30 | 0 | this.collectionName = collectionName; |
| 31 | 0 | this.spec = spec; |
| 32 | 0 | this.document = document; |
| 33 | 0 | this.db = db; |
| 34 | 0 | this.serializeFunctions = false; |
| 35 | 0 | this.checkKeys = typeof options.checkKeys != 'boolean' ? false : options.checkKeys; |
| 36 | ||
| 37 | // Generate correct flags | |
| 38 | 0 | var db_upsert = 0; |
| 39 | 0 | var db_multi_update = 0; |
| 40 | 0 | db_upsert = options != null && options['upsert'] != null ? (options['upsert'] == true ? 1 : 0) : db_upsert; |
| 41 | 0 | db_multi_update = options != null && options['multi'] != null ? (options['multi'] == true ? 1 : 0) : db_multi_update; |
| 42 | ||
| 43 | // Flags | |
| 44 | 0 | this.flags = parseInt(db_multi_update.toString() + db_upsert.toString(), 2); |
| 45 | // Let us defined on a command basis if we want functions to be serialized or not | |
| 46 | 0 | if(options['serializeFunctions'] != null && options['serializeFunctions']) { |
| 47 | 0 | this.serializeFunctions = true; |
| 48 | } | |
| 49 | }; | |
| 50 | ||
| 51 | 1 | inherits(UpdateCommand, BaseCommand); |
| 52 | ||
| 53 | 1 | UpdateCommand.OP_UPDATE = 2001; |
| 54 | ||
| 55 | /* | |
| 56 | struct { | |
| 57 | MsgHeader header; // standard message header | |
| 58 | int32 ZERO; // 0 - reserved for future use | |
| 59 | cstring fullCollectionName; // "dbname.collectionname" | |
| 60 | int32 flags; // bit vector. see below | |
| 61 | BSON spec; // the query to select the document | |
| 62 | BSON document; // the document data to update with or insert | |
| 63 | } | |
| 64 | */ | |
| 65 | 1 | UpdateCommand.prototype.toBinary = function(bsonSettings) { |
| 66 | // Validate that we are not passing 0x00 in the colletion name | |
| 67 | 0 | if(!!~this.collectionName.indexOf("\x00")) { |
| 68 | 0 | throw new Error("namespace cannot contain a null character"); |
| 69 | } | |
| 70 | ||
| 71 | // Calculate total length of the document | |
| 72 | 0 | var totalLengthOfCommand = 4 + Buffer.byteLength(this.collectionName) + 1 + 4 + this.db.bson.calculateObjectSize(this.spec, false, true) + |
| 73 | this.db.bson.calculateObjectSize(this.document, this.serializeFunctions, true) + (4 * 4); | |
| 74 | ||
| 75 | // Enforce maximum bson size | |
| 76 | 0 | if(!bsonSettings.disableDriverBSONSizeCheck |
| 77 | && totalLengthOfCommand > bsonSettings.maxBsonSize) | |
| 78 | 0 | throw new Error("Document exceeds maximum allowed bson size of " + bsonSettings.maxBsonSize + " bytes"); |
| 79 | ||
| 80 | 0 | if(bsonSettings.disableDriverBSONSizeCheck |
| 81 | && totalLengthOfCommand > bsonSettings.maxMessageSizeBytes) | |
| 82 | 0 | throw new Error("Command exceeds maximum message size of " + bsonSettings.maxMessageSizeBytes + " bytes"); |
| 83 | ||
| 84 | // Let's build the single pass buffer command | |
| 85 | 0 | var _index = 0; |
| 86 | 0 | var _command = new Buffer(totalLengthOfCommand); |
| 87 | // Write the header information to the buffer | |
| 88 | 0 | _command[_index + 3] = (totalLengthOfCommand >> 24) & 0xff; |
| 89 | 0 | _command[_index + 2] = (totalLengthOfCommand >> 16) & 0xff; |
| 90 | 0 | _command[_index + 1] = (totalLengthOfCommand >> 8) & 0xff; |
| 91 | 0 | _command[_index] = totalLengthOfCommand & 0xff; |
| 92 | // Adjust index | |
| 93 | 0 | _index = _index + 4; |
| 94 | // Write the request ID | |
| 95 | 0 | _command[_index + 3] = (this.requestId >> 24) & 0xff; |
| 96 | 0 | _command[_index + 2] = (this.requestId >> 16) & 0xff; |
| 97 | 0 | _command[_index + 1] = (this.requestId >> 8) & 0xff; |
| 98 | 0 | _command[_index] = this.requestId & 0xff; |
| 99 | // Adjust index | |
| 100 | 0 | _index = _index + 4; |
| 101 | // Write zero | |
| 102 | 0 | _command[_index++] = 0; |
| 103 | 0 | _command[_index++] = 0; |
| 104 | 0 | _command[_index++] = 0; |
| 105 | 0 | _command[_index++] = 0; |
| 106 | // Write the op_code for the command | |
| 107 | 0 | _command[_index + 3] = (UpdateCommand.OP_UPDATE >> 24) & 0xff; |
| 108 | 0 | _command[_index + 2] = (UpdateCommand.OP_UPDATE >> 16) & 0xff; |
| 109 | 0 | _command[_index + 1] = (UpdateCommand.OP_UPDATE >> 8) & 0xff; |
| 110 | 0 | _command[_index] = UpdateCommand.OP_UPDATE & 0xff; |
| 111 | // Adjust index | |
| 112 | 0 | _index = _index + 4; |
| 113 | ||
| 114 | // Write zero | |
| 115 | 0 | _command[_index++] = 0; |
| 116 | 0 | _command[_index++] = 0; |
| 117 | 0 | _command[_index++] = 0; |
| 118 | 0 | _command[_index++] = 0; |
| 119 | ||
| 120 | // Write the collection name to the command | |
| 121 | 0 | _index = _index + _command.write(this.collectionName, _index, 'utf8') + 1; |
| 122 | 0 | _command[_index - 1] = 0; |
| 123 | ||
| 124 | // Write the update flags | |
| 125 | 0 | _command[_index + 3] = (this.flags >> 24) & 0xff; |
| 126 | 0 | _command[_index + 2] = (this.flags >> 16) & 0xff; |
| 127 | 0 | _command[_index + 1] = (this.flags >> 8) & 0xff; |
| 128 | 0 | _command[_index] = this.flags & 0xff; |
| 129 | // Adjust index | |
| 130 | 0 | _index = _index + 4; |
| 131 | ||
| 132 | // Document binary length | |
| 133 | 0 | var documentLength = 0 |
| 134 | 0 | var object = this.spec; |
| 135 | ||
| 136 | // Serialize the selector | |
| 137 | // If we are passing a raw buffer, do minimal validation | |
| 138 | 0 | if(Buffer.isBuffer(object)) { |
| 139 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 140 | 0 | if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 141 | 0 | documentLength = object.length; |
| 142 | // Copy the data into the current buffer | |
| 143 | 0 | object.copy(_command, _index); |
| 144 | } else { | |
| 145 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, this.checkKeys, _command, _index, false) - _index + 1; |
| 146 | } | |
| 147 | ||
| 148 | // Write the length to the document | |
| 149 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 150 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 151 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 152 | 0 | _command[_index] = documentLength & 0xff; |
| 153 | // Update index in buffer | |
| 154 | 0 | _index = _index + documentLength; |
| 155 | // Add terminating 0 for the object | |
| 156 | 0 | _command[_index - 1] = 0; |
| 157 | ||
| 158 | // Document binary length | |
| 159 | 0 | var documentLength = 0 |
| 160 | 0 | var object = this.document; |
| 161 | ||
| 162 | // Serialize the document | |
| 163 | // If we are passing a raw buffer, do minimal validation | |
| 164 | 0 | if(Buffer.isBuffer(object)) { |
| 165 | 0 | var object_size = object[0] | object[1] << 8 | object[2] << 16 | object[3] << 24; |
| 166 | 0 | if(object_size != object.length) throw new Error("raw message size does not match message header size [" + object.length + "] != [" + object_size + "]"); |
| 167 | 0 | documentLength = object.length; |
| 168 | // Copy the data into the current buffer | |
| 169 | 0 | object.copy(_command, _index); |
| 170 | } else { | |
| 171 | 0 | documentLength = this.db.bson.serializeWithBufferAndIndex(object, false, _command, _index, this.serializeFunctions) - _index + 1; |
| 172 | } | |
| 173 | ||
| 174 | // Write the length to the document | |
| 175 | 0 | _command[_index + 3] = (documentLength >> 24) & 0xff; |
| 176 | 0 | _command[_index + 2] = (documentLength >> 16) & 0xff; |
| 177 | 0 | _command[_index + 1] = (documentLength >> 8) & 0xff; |
| 178 | 0 | _command[_index] = documentLength & 0xff; |
| 179 | // Update index in buffer | |
| 180 | 0 | _index = _index + documentLength; |
| 181 | // Add terminating 0 for the object | |
| 182 | 0 | _command[_index - 1] = 0; |
| 183 | ||
| 184 | 0 | return _command; |
| 185 | }; | |
| 186 | ||
| 187 | // Constants | |
| 188 | 1 | UpdateCommand.DB_UPSERT = 0; |
| 189 | 1 | UpdateCommand.DB_MULTI_UPDATE = 1; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var EventEmitter = require('events').EventEmitter |
| 2 | , inherits = require('util').inherits | |
| 3 | , utils = require('../utils') | |
| 4 | , mongodb_cr_authenticate = require('../auth/mongodb_cr.js').authenticate | |
| 5 | , mongodb_gssapi_authenticate = require('../auth/mongodb_gssapi.js').authenticate | |
| 6 | , mongodb_sspi_authenticate = require('../auth/mongodb_sspi.js').authenticate | |
| 7 | , mongodb_plain_authenticate = require('../auth/mongodb_plain.js').authenticate | |
| 8 | , mongodb_x509_authenticate = require('../auth/mongodb_x509.js').authenticate; | |
| 9 | ||
| 10 | 1 | var id = 0; |
| 11 | ||
| 12 | /** | |
| 13 | * Internal class for callback storage | |
| 14 | * @ignore | |
| 15 | */ | |
| 16 | 1 | var CallbackStore = function() { |
| 17 | // Make class an event emitter | |
| 18 | 2 | EventEmitter.call(this); |
| 19 | // Add a info about call variable | |
| 20 | 2 | this._notReplied = {}; |
| 21 | 2 | this.id = id++; |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | */ | |
| 27 | 1 | inherits(CallbackStore, EventEmitter); |
| 28 | ||
| 29 | 1 | CallbackStore.prototype.notRepliedToIds = function() { |
| 30 | 0 | return Object.keys(this._notReplied); |
| 31 | } | |
| 32 | ||
| 33 | 1 | CallbackStore.prototype.callbackInfo = function(id) { |
| 34 | 0 | return this._notReplied[id]; |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Internal class for holding non-executed commands | |
| 39 | * @ignore | |
| 40 | */ | |
| 41 | 1 | var NonExecutedOperationStore = function(config) { |
| 42 | 1 | var commands = { |
| 43 | read: [] | |
| 44 | , write_reads: [] | |
| 45 | , write: [] | |
| 46 | }; | |
| 47 | ||
| 48 | // Execute all callbacks | |
| 49 | 1 | var fireCallbacksWithError = function(error, commands) { |
| 50 | 0 | while(commands.length > 0) { |
| 51 | 0 | var command = commands.shift(); |
| 52 | 0 | if(typeof command.callback == 'function') { |
| 53 | 0 | command.callback(error); |
| 54 | } | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | 1 | this.count = function() { |
| 59 | 0 | return commands.read.length |
| 60 | + commands.write_reads.length | |
| 61 | + commands.write.length; | |
| 62 | } | |
| 63 | ||
| 64 | 1 | this.write = function(op) { |
| 65 | 0 | commands.write.push(op); |
| 66 | } | |
| 67 | ||
| 68 | 1 | this.read_from_writer = function(op) { |
| 69 | 0 | commands.write_reads.push(op); |
| 70 | } | |
| 71 | ||
| 72 | 1 | this.read = function(op) { |
| 73 | 0 | commands.read.push(op); |
| 74 | } | |
| 75 | ||
| 76 | 1 | this.validateBufferLimit = function(numberToFailOn) { |
| 77 | 0 | if(numberToFailOn == -1 || numberToFailOn == null) |
| 78 | 0 | return true; |
| 79 | ||
| 80 | // Error passed back | |
| 81 | 0 | var error = utils.toError("No connection operations buffering limit of " + numberToFailOn + " reached"); |
| 82 | ||
| 83 | // If we have passed the number of items to buffer we need to fail | |
| 84 | 0 | if(numberToFailOn < this.count()) { |
| 85 | // Fail all of the callbacks | |
| 86 | 0 | fireCallbacksWithError(error, commands.read); |
| 87 | 0 | fireCallbacksWithError(error, commands.write_reads); |
| 88 | 0 | fireCallbacksWithError(error, commands.write); |
| 89 | } | |
| 90 | ||
| 91 | // Return false | |
| 92 | 0 | return false; |
| 93 | } | |
| 94 | ||
| 95 | 1 | this.execute_queries = function(executeInsertCommand) { |
| 96 | 0 | var connection = config.checkoutReader(); |
| 97 | 0 | if(connection == null || connection instanceof Error) return; |
| 98 | ||
| 99 | // Write out all the queries | |
| 100 | 0 | while(commands.read.length > 0) { |
| 101 | // Get the next command | |
| 102 | 0 | var command = commands.read.shift(); |
| 103 | 0 | command.options.connection = connection; |
| 104 | // Execute the next command | |
| 105 | 0 | command.executeQueryCommand(command.db, command.db_command, command.options, command.callback); |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | 1 | this.execute_writes = function() { |
| 110 | 0 | var connection = config.checkoutWriter(); |
| 111 | 0 | if(connection == null || connection instanceof Error) return; |
| 112 | ||
| 113 | // Write out all the queries to the primary | |
| 114 | 0 | while(commands.write_reads.length > 0) { |
| 115 | // Get the next command | |
| 116 | 0 | var command = commands.write_reads.shift(); |
| 117 | 0 | command.options.connection = connection; |
| 118 | // Execute the next command | |
| 119 | 0 | command.executeQueryCommand(command.db, command.db_command, command.options, command.callback); |
| 120 | } | |
| 121 | ||
| 122 | // Execute all write operations | |
| 123 | 0 | while(commands.write.length > 0) { |
| 124 | // Get the next command | |
| 125 | 0 | var command = commands.write.shift(); |
| 126 | // Set the connection | |
| 127 | 0 | command.options.connection = connection; |
| 128 | // Execute the next command | |
| 129 | 0 | command.executeInsertCommand(command.db, command.db_command, command.options, command.callback); |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | /** | |
| 135 | * Internal class for authentication storage | |
| 136 | * @ignore | |
| 137 | */ | |
| 138 | 1 | var AuthStore = function() { |
| 139 | 1 | var _auths = []; |
| 140 | ||
| 141 | 1 | this.add = function(authMechanism, dbName, username, password, authdbName, gssapiServiceName) { |
| 142 | // Check for duplicates | |
| 143 | 0 | if(!this.contains(dbName)) { |
| 144 | // Base config | |
| 145 | 0 | var config = { |
| 146 | 'username':username | |
| 147 | , 'password':password | |
| 148 | , 'db': dbName | |
| 149 | , 'authMechanism': authMechanism | |
| 150 | , 'gssapiServiceName': gssapiServiceName | |
| 151 | }; | |
| 152 | ||
| 153 | // Add auth source if passed in | |
| 154 | 0 | if(typeof authdbName == 'string') { |
| 155 | 0 | config['authdb'] = authdbName; |
| 156 | } | |
| 157 | ||
| 158 | // Push the config | |
| 159 | 0 | _auths.push(config); |
| 160 | } | |
| 161 | } | |
| 162 | ||
| 163 | 1 | this.contains = function(dbName) { |
| 164 | 0 | for(var i = 0; i < _auths.length; i++) { |
| 165 | 0 | if(_auths[i].db == dbName) return true; |
| 166 | } | |
| 167 | ||
| 168 | 0 | return false; |
| 169 | } | |
| 170 | ||
| 171 | 1 | this.remove = function(dbName) { |
| 172 | 0 | var newAuths = []; |
| 173 | ||
| 174 | // Filter out all the login details | |
| 175 | 0 | for(var i = 0; i < _auths.length; i++) { |
| 176 | 0 | if(_auths[i].db != dbName) newAuths.push(_auths[i]); |
| 177 | } | |
| 178 | ||
| 179 | // Set the filtered list | |
| 180 | 0 | _auths = newAuths; |
| 181 | } | |
| 182 | ||
| 183 | 1 | this.get = function(index) { |
| 184 | 0 | return _auths[index]; |
| 185 | } | |
| 186 | ||
| 187 | 1 | this.length = function() { |
| 188 | 0 | return _auths.length; |
| 189 | } | |
| 190 | ||
| 191 | 1 | this.toArray = function() { |
| 192 | 0 | return _auths.slice(0); |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | /** | |
| 197 | * Internal class for storing db references | |
| 198 | * @ignore | |
| 199 | */ | |
| 200 | 1 | var DbStore = function() { |
| 201 | 1 | var _dbs = []; |
| 202 | ||
| 203 | 1 | this.add = function(db) { |
| 204 | 1 | var found = false; |
| 205 | ||
| 206 | // Only add if it does not exist already | |
| 207 | 1 | for(var i = 0; i < _dbs.length; i++) { |
| 208 | 0 | if(db.databaseName == _dbs[i].databaseName) found = true; |
| 209 | } | |
| 210 | ||
| 211 | // Only add if it does not already exist | |
| 212 | 1 | if(!found) { |
| 213 | 1 | _dbs.push(db); |
| 214 | } | |
| 215 | } | |
| 216 | ||
| 217 | 1 | this.reset = function() { |
| 218 | 0 | _dbs = []; |
| 219 | } | |
| 220 | ||
| 221 | 1 | this.db = function() { |
| 222 | 0 | return _dbs; |
| 223 | } | |
| 224 | ||
| 225 | 1 | this.fetch = function(databaseName) { |
| 226 | // Only add if it does not exist already | |
| 227 | 0 | for(var i = 0; i < _dbs.length; i++) { |
| 228 | 0 | if(databaseName == _dbs[i].databaseName) |
| 229 | 0 | return _dbs[i]; |
| 230 | } | |
| 231 | ||
| 232 | 0 | return null; |
| 233 | } | |
| 234 | ||
| 235 | 1 | this.emit = function(event, message, object, reset, filterDb, rethrow_if_no_listeners) { |
| 236 | 0 | var emitted = false; |
| 237 | ||
| 238 | // Not emitted and we have enabled rethrow, let process.uncaughtException | |
| 239 | // deal with the issue | |
| 240 | 0 | if(!emitted && rethrow_if_no_listeners) { |
| 241 | 0 | return process.nextTick(function() { |
| 242 | 0 | throw message; |
| 243 | }) | |
| 244 | } | |
| 245 | ||
| 246 | // Emit the events | |
| 247 | 0 | for(var i = 0; i < _dbs.length; i++) { |
| 248 | 0 | if(_dbs[i].listeners(event).length > 0) { |
| 249 | 0 | if(filterDb == null || filterDb.databaseName !== _dbs[i].databaseName |
| 250 | || filterDb.tag !== _dbs[i].tag) { | |
| 251 | 0 | _dbs[i].emit(event, message, object == null ? _dbs[i] : object); |
| 252 | 0 | emitted = true; |
| 253 | } | |
| 254 | } | |
| 255 | } | |
| 256 | ||
| 257 | // Emit error message | |
| 258 | 0 | if(message |
| 259 | && event == 'error' | |
| 260 | && !emitted | |
| 261 | && rethrow_if_no_listeners | |
| 262 | && object && object.db) { | |
| 263 | 0 | process.nextTick(function() { |
| 264 | 0 | object.db.emit(event, message, null); |
| 265 | }) | |
| 266 | } | |
| 267 | } | |
| 268 | } | |
| 269 | ||
| 270 | 1 | var Base = function Base() { |
| 271 | 1 | EventEmitter.call(this); |
| 272 | ||
| 273 | // Callback store is part of connection specification | |
| 274 | 1 | if(Base._callBackStore == null) { |
| 275 | 1 | Base._callBackStore = new CallbackStore(); |
| 276 | } | |
| 277 | ||
| 278 | // Create a new callback store | |
| 279 | 1 | this._callBackStore = new CallbackStore(); |
| 280 | // All commands not being executed | |
| 281 | 1 | this._commandsStore = new NonExecutedOperationStore(this); |
| 282 | // Create a new auth store | |
| 283 | 1 | this.auth = new AuthStore(); |
| 284 | // Contains all the dbs attached to this server config | |
| 285 | 1 | this._dbStore = new DbStore(); |
| 286 | } | |
| 287 | ||
| 288 | /** | |
| 289 | * @ignore | |
| 290 | */ | |
| 291 | 1 | inherits(Base, EventEmitter); |
| 292 | ||
| 293 | /** | |
| 294 | * @ignore | |
| 295 | */ | |
| 296 | 1 | Base.prototype._apply_auths = function(db, callback) { |
| 297 | 0 | _apply_auths_serially(this, db, this.auth.toArray(), callback); |
| 298 | } | |
| 299 | ||
| 300 | 1 | var _apply_auths_serially = function(self, db, auths, callback) { |
| 301 | 0 | if(auths.length == 0) return callback(null, null); |
| 302 | // Get the first auth | |
| 303 | 0 | var auth = auths.shift(); |
| 304 | 0 | var connections = self.allRawConnections(); |
| 305 | 0 | var connectionsLeft = connections.length; |
| 306 | 0 | var options = {}; |
| 307 | ||
| 308 | 0 | if(auth.authMechanism == 'GSSAPI') { |
| 309 | // We have the kerberos library, execute auth process | |
| 310 | 0 | if(process.platform == 'win32') { |
| 311 | 0 | mongodb_sspi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 312 | } else { | |
| 313 | 0 | mongodb_gssapi_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 314 | } | |
| 315 | 0 | } else if(auth.authMechanism == 'MONGODB-CR') { |
| 316 | 0 | mongodb_cr_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 317 | 0 | } else if(auth.authMechanism == 'PLAIN') { |
| 318 | 0 | mongodb_plain_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 319 | 0 | } else if(auth.authMechanism == 'MONGODB-X509') { |
| 320 | 0 | mongodb_x509_authenticate(db, auth.username, auth.password, auth.authdb, options, callback); |
| 321 | } | |
| 322 | } | |
| 323 | ||
| 324 | /** | |
| 325 | * Fire all the errors | |
| 326 | * @ignore | |
| 327 | */ | |
| 328 | 1 | Base.prototype.__executeAllCallbacksWithError = function(err) { |
| 329 | // Check all callbacks | |
| 330 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 331 | // For each key check if it's a callback that needs to be returned | |
| 332 | 0 | for(var j = 0; j < keys.length; j++) { |
| 333 | 0 | var info = this._callBackStore._notReplied[keys[j]]; |
| 334 | // Execute callback with error | |
| 335 | 0 | this._callBackStore.emit(keys[j], err, null); |
| 336 | // Remove the key | |
| 337 | 0 | delete this._callBackStore._notReplied[keys[j]]; |
| 338 | // Force cleanup _events, node.js seems to set it as a null value | |
| 339 | 0 | if(this._callBackStore._events) { |
| 340 | 0 | delete this._callBackStore._events[keys[j]]; |
| 341 | } | |
| 342 | } | |
| 343 | } | |
| 344 | ||
| 345 | /** | |
| 346 | * Fire all the errors | |
| 347 | * @ignore | |
| 348 | */ | |
| 349 | 1 | Base.prototype.__executeAllServerSpecificErrorCallbacks = function(host, port, err) { |
| 350 | // Check all callbacks | |
| 351 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 352 | // For each key check if it's a callback that needs to be returned | |
| 353 | 0 | for(var j = 0; j < keys.length; j++) { |
| 354 | 0 | var info = this._callBackStore._notReplied[keys[j]]; |
| 355 | ||
| 356 | 0 | if(info.connection) { |
| 357 | // Unpack the connection settings | |
| 358 | 0 | var _host = info.connection.socketOptions.host; |
| 359 | 0 | var _port = info.connection.socketOptions.port; |
| 360 | // If the server matches execute the callback with the error | |
| 361 | 0 | if(_port == port && _host == host) { |
| 362 | 0 | this._callBackStore.emit(keys[j], err, null); |
| 363 | // Remove the key | |
| 364 | 0 | delete this._callBackStore._notReplied[keys[j]]; |
| 365 | // Force cleanup _events, node.js seems to set it as a null value | |
| 366 | 0 | if(this._callBackStore._events) { |
| 367 | 0 | delete this._callBackStore._events[keys[j]]; |
| 368 | } | |
| 369 | } | |
| 370 | } | |
| 371 | } | |
| 372 | } | |
| 373 | ||
| 374 | /** | |
| 375 | * Register a handler | |
| 376 | * @ignore | |
| 377 | * @api private | |
| 378 | */ | |
| 379 | 1 | Base.prototype._registerHandler = function(db_command, raw, connection, exhaust, callback) { |
| 380 | // Check if we have exhausted | |
| 381 | 0 | if(typeof exhaust == 'function') { |
| 382 | 0 | callback = exhaust; |
| 383 | 0 | exhaust = false; |
| 384 | } | |
| 385 | ||
| 386 | // Add the callback to the list of handlers | |
| 387 | 0 | this._callBackStore.once(db_command.getRequestId(), callback); |
| 388 | // Add the information about the reply | |
| 389 | 0 | this._callBackStore._notReplied[db_command.getRequestId().toString()] = {start: new Date().getTime(), 'raw': raw, connection:connection, exhaust:exhaust}; |
| 390 | } | |
| 391 | ||
| 392 | /** | |
| 393 | * Re-Register a handler, on the cursor id f.ex | |
| 394 | * @ignore | |
| 395 | * @api private | |
| 396 | */ | |
| 397 | 1 | Base.prototype._reRegisterHandler = function(newId, object, callback) { |
| 398 | // Add the callback to the list of handlers | |
| 399 | 0 | this._callBackStore.once(newId, object.callback.listener); |
| 400 | // Add the information about the reply | |
| 401 | 0 | this._callBackStore._notReplied[newId] = object.info; |
| 402 | } | |
| 403 | ||
| 404 | /** | |
| 405 | * | |
| 406 | * @ignore | |
| 407 | * @api private | |
| 408 | */ | |
| 409 | 1 | Base.prototype._flushAllCallHandlers = function(err) { |
| 410 | 0 | var keys = Object.keys(this._callBackStore._notReplied); |
| 411 | ||
| 412 | 0 | for(var i = 0; i < keys.length; i++) { |
| 413 | 0 | this._callHandler(keys[i], null, err); |
| 414 | } | |
| 415 | } | |
| 416 | ||
| 417 | /** | |
| 418 | * | |
| 419 | * @ignore | |
| 420 | * @api private | |
| 421 | */ | |
| 422 | 1 | Base.prototype._callHandler = function(id, document, err) { |
| 423 | 0 | var self = this; |
| 424 | ||
| 425 | // If there is a callback peform it | |
| 426 | 0 | if(this._callBackStore.listeners(id).length >= 1) { |
| 427 | // Get info object | |
| 428 | 0 | var info = this._callBackStore._notReplied[id]; |
| 429 | // Delete the current object | |
| 430 | 0 | delete this._callBackStore._notReplied[id]; |
| 431 | // Call the handle directly don't emit | |
| 432 | 0 | var callback = this._callBackStore.listeners(id)[0].listener; |
| 433 | // Remove the listeners | |
| 434 | 0 | this._callBackStore.removeAllListeners(id); |
| 435 | // Force key deletion because it nulling it not deleting in 0.10.X | |
| 436 | 0 | if(this._callBackStore._events) { |
| 437 | 0 | delete this._callBackStore._events[id]; |
| 438 | } | |
| 439 | ||
| 440 | 0 | try { |
| 441 | // Execute the callback if one was provided | |
| 442 | 0 | if(typeof callback == 'function') callback(err, document, info.connection); |
| 443 | } catch(err) { | |
| 444 | 0 | self._emitAcrossAllDbInstances(self, null, "error", utils.toError(err), self, true, true); |
| 445 | } | |
| 446 | } | |
| 447 | } | |
| 448 | ||
| 449 | /** | |
| 450 | * | |
| 451 | * @ignore | |
| 452 | * @api private | |
| 453 | */ | |
| 454 | 1 | Base.prototype._hasHandler = function(id) { |
| 455 | 0 | return this._callBackStore.listeners(id).length >= 1; |
| 456 | } | |
| 457 | ||
| 458 | /** | |
| 459 | * | |
| 460 | * @ignore | |
| 461 | * @api private | |
| 462 | */ | |
| 463 | 1 | Base.prototype._removeHandler = function(id) { |
| 464 | // Remove the information | |
| 465 | 0 | if(this._callBackStore._notReplied[id] != null) delete this._callBackStore._notReplied[id]; |
| 466 | // Remove the callback if it's registered | |
| 467 | 0 | this._callBackStore.removeAllListeners(id); |
| 468 | // Force cleanup _events, node.js seems to set it as a null value | |
| 469 | 0 | if(this._callBackStore._events) { |
| 470 | 0 | delete this._callBackStore._events[id]; |
| 471 | } | |
| 472 | } | |
| 473 | ||
| 474 | /** | |
| 475 | * | |
| 476 | * @ignore | |
| 477 | * @api private | |
| 478 | */ | |
| 479 | 1 | Base.prototype._findHandler = function(id) { |
| 480 | 0 | var info = this._callBackStore._notReplied[id]; |
| 481 | // Return the callback | |
| 482 | 0 | return {info:info, callback:(this._callBackStore.listeners(id).length >= 1) ? this._callBackStore.listeners(id)[0] : null} |
| 483 | } | |
| 484 | ||
| 485 | /** | |
| 486 | * | |
| 487 | * @ignore | |
| 488 | * @api private | |
| 489 | */ | |
| 490 | 1 | Base.prototype._emitAcrossAllDbInstances = function(server, filterDb, event, message, object, resetConnection, rethrow_if_no_listeners) { |
| 491 | 0 | if(resetConnection) { |
| 492 | 0 | var dbs = this._dbStore.db(); |
| 493 | ||
| 494 | 0 | for(var i = 0; i < dbs.length; i++) { |
| 495 | 0 | if(typeof dbs[i].openCalled != 'undefined') |
| 496 | 0 | dbs[i].openCalled = false; |
| 497 | } | |
| 498 | } | |
| 499 | ||
| 500 | // Fire event | |
| 501 | 0 | this._dbStore.emit(event, message, object, resetConnection, filterDb, rethrow_if_no_listeners); |
| 502 | } | |
| 503 | ||
| 504 | 1 | exports.Base = Base; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var utils = require('./connection_utils'), |
| 2 | inherits = require('util').inherits, | |
| 3 | net = require('net'), | |
| 4 | EventEmitter = require('events').EventEmitter, | |
| 5 | inherits = require('util').inherits, | |
| 6 | binaryutils = require('../utils'), | |
| 7 | tls = require('tls'); | |
| 8 | ||
| 9 | 1 | var Connection = exports.Connection = function(id, socketOptions) { |
| 10 | 1 | var self = this; |
| 11 | // Set up event emitter | |
| 12 | 1 | EventEmitter.call(this); |
| 13 | // Store all socket options | |
| 14 | 1 | this.socketOptions = socketOptions ? socketOptions : {host:'localhost', port:27017, domainSocket:false}; |
| 15 | // Set keep alive default if not overriden | |
| 16 | 1 | if(this.socketOptions.keepAlive == null && (process.platform !== "sunos" || process.platform !== "win32")) this.socketOptions.keepAlive = 100; |
| 17 | // Id for the connection | |
| 18 | 1 | this.id = id; |
| 19 | // State of the connection | |
| 20 | 1 | this.connected = false; |
| 21 | // Set if this is a domain socket | |
| 22 | 1 | this.domainSocket = this.socketOptions.domainSocket; |
| 23 | ||
| 24 | // Supported min and max wire protocol | |
| 25 | 1 | this.minWireVersion = 0; |
| 26 | 1 | this.maxWireVersion = 2; |
| 27 | ||
| 28 | // | |
| 29 | // Connection parsing state | |
| 30 | // | |
| 31 | 1 | this.maxBsonSize = socketOptions.maxBsonSize ? socketOptions.maxBsonSize : Connection.DEFAULT_MAX_BSON_SIZE; |
| 32 | 1 | this.maxMessageSizeBytes = socketOptions.maxMessageSizeBytes ? socketOptions.maxMessageSizeBytes : Connection.DEFAULT_MAX_MESSAGE_SIZE; |
| 33 | // Contains the current message bytes | |
| 34 | 1 | this.buffer = null; |
| 35 | // Contains the current message size | |
| 36 | 1 | this.sizeOfMessage = 0; |
| 37 | // Contains the readIndex for the messaage | |
| 38 | 1 | this.bytesRead = 0; |
| 39 | // Contains spill over bytes from additional messages | |
| 40 | 1 | this.stubBuffer = 0; |
| 41 | ||
| 42 | // Just keeps list of events we allow | |
| 43 | 1 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[], end:[]}; |
| 44 | ||
| 45 | // Just keeps list of events we allow | |
| 46 | 1 | resetHandlers(this, false); |
| 47 | // Bson object | |
| 48 | 1 | this.maxBsonSettings = { |
| 49 | disableDriverBSONSizeCheck: this.socketOptions['disableDriverBSONSizeCheck'] || false | |
| 50 | , maxBsonSize: this.maxBsonSize | |
| 51 | , maxMessageSizeBytes: this.maxMessageSizeBytes | |
| 52 | } | |
| 53 | ||
| 54 | // Allow setting the socketTimeoutMS on all connections | |
| 55 | // to work around issues such as secondaries blocking due to compaction | |
| 56 | 1 | Object.defineProperty(this, "socketTimeoutMS", { |
| 57 | enumerable: true | |
| 58 | 0 | , get: function () { return self.socketOptions.socketTimeoutMS; } |
| 59 | , set: function (value) { | |
| 60 | // Set the socket timeoutMS value | |
| 61 | 0 | self.socketOptions.socketTimeoutMS = value; |
| 62 | // Set the physical connection timeout | |
| 63 | 0 | self.connection.setTimeout(self.socketOptions.socketTimeoutMS); |
| 64 | } | |
| 65 | }); | |
| 66 | } | |
| 67 | ||
| 68 | // Set max bson size | |
| 69 | 1 | Connection.DEFAULT_MAX_BSON_SIZE = 1024 * 1024 * 4; |
| 70 | // Set default to max bson to avoid overflow or bad guesses | |
| 71 | 1 | Connection.DEFAULT_MAX_MESSAGE_SIZE = Connection.DEFAULT_MAX_BSON_SIZE; |
| 72 | ||
| 73 | // Inherit event emitter so we can emit stuff wohoo | |
| 74 | 1 | inherits(Connection, EventEmitter); |
| 75 | ||
| 76 | 1 | Connection.prototype.start = function() { |
| 77 | 1 | var self = this; |
| 78 | ||
| 79 | // If we have a normal connection | |
| 80 | 1 | if(this.socketOptions.ssl) { |
| 81 | // Create new connection instance | |
| 82 | 0 | if(this.domainSocket) { |
| 83 | 0 | this.connection = net.createConnection(this.socketOptions.host); |
| 84 | } else { | |
| 85 | 0 | this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); |
| 86 | } | |
| 87 | 0 | if(this.logger != null && this.logger.doDebug){ |
| 88 | 0 | this.logger.debug("opened connection", this.socketOptions); |
| 89 | } | |
| 90 | ||
| 91 | // Set options on the socket | |
| 92 | 0 | this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout); |
| 93 | // Work around for 0.4.X | |
| 94 | 0 | if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); |
| 95 | // Set keep alive if defined | |
| 96 | 0 | if(process.version.indexOf("v0.4") == -1) { |
| 97 | 0 | if(this.socketOptions.keepAlive > 0) { |
| 98 | 0 | this.connection.setKeepAlive(true, this.socketOptions.keepAlive); |
| 99 | } else { | |
| 100 | 0 | this.connection.setKeepAlive(false); |
| 101 | } | |
| 102 | } | |
| 103 | ||
| 104 | // Check if the driver should validate the certificate | |
| 105 | 0 | var validate_certificates = this.socketOptions.sslValidate == true ? true : false; |
| 106 | ||
| 107 | // Create options for the tls connection | |
| 108 | 0 | var tls_options = { |
| 109 | socket: this.connection | |
| 110 | , rejectUnauthorized: false | |
| 111 | } | |
| 112 | ||
| 113 | // If we wish to validate the certificate we have provided a ca store | |
| 114 | 0 | if(validate_certificates) { |
| 115 | 0 | tls_options.ca = this.socketOptions.sslCA; |
| 116 | } | |
| 117 | ||
| 118 | // If we have a certificate to present | |
| 119 | 0 | if(this.socketOptions.sslCert) { |
| 120 | 0 | tls_options.cert = this.socketOptions.sslCert; |
| 121 | 0 | tls_options.key = this.socketOptions.sslKey; |
| 122 | } | |
| 123 | ||
| 124 | // If the driver has been provided a private key password | |
| 125 | 0 | if(this.socketOptions.sslPass) { |
| 126 | 0 | tls_options.passphrase = this.socketOptions.sslPass; |
| 127 | } | |
| 128 | ||
| 129 | // Contains the cleartext stream | |
| 130 | 0 | var cleartext = null; |
| 131 | // Attempt to establish a TLS connection to the server | |
| 132 | 0 | try { |
| 133 | 0 | cleartext = tls.connect(this.socketOptions.port, this.socketOptions.host, tls_options, function() { |
| 134 | // If we have a ssl certificate validation error return an error | |
| 135 | 0 | if(cleartext.authorizationError && validate_certificates) { |
| 136 | // Emit an error | |
| 137 | 0 | return self.emit("error", cleartext.authorizationError, self, {ssl:true}); |
| 138 | } | |
| 139 | ||
| 140 | // Connect to the server | |
| 141 | 0 | connectHandler(self)(); |
| 142 | }) | |
| 143 | } catch(err) { | |
| 144 | 0 | return self.emit("error", "SSL connection failed", self, {ssl:true}); |
| 145 | } | |
| 146 | ||
| 147 | // Save the output stream | |
| 148 | 0 | this.writeSteam = cleartext; |
| 149 | ||
| 150 | // Set up data handler for the clear stream | |
| 151 | 0 | cleartext.on("data", createDataHandler(this)); |
| 152 | // Do any handling of end event of the stream | |
| 153 | 0 | cleartext.on("end", endHandler(this)); |
| 154 | 0 | cleartext.on("error", errorHandler(this)); |
| 155 | ||
| 156 | // Handle any errors | |
| 157 | 0 | this.connection.on("error", errorHandler(this)); |
| 158 | // Handle timeout | |
| 159 | 0 | this.connection.on("timeout", timeoutHandler(this)); |
| 160 | // Handle drain event | |
| 161 | 0 | this.connection.on("drain", drainHandler(this)); |
| 162 | // Handle the close event | |
| 163 | 0 | this.connection.on("close", closeHandler(this)); |
| 164 | } else { | |
| 165 | // Create new connection instance | |
| 166 | 1 | if(this.domainSocket) { |
| 167 | 0 | this.connection = net.createConnection(this.socketOptions.host); |
| 168 | } else { | |
| 169 | 1 | this.connection = net.createConnection(this.socketOptions.port, this.socketOptions.host); |
| 170 | } | |
| 171 | 1 | if(this.logger != null && this.logger.doDebug){ |
| 172 | 0 | this.logger.debug("opened connection", this.socketOptions); |
| 173 | } | |
| 174 | ||
| 175 | // Set options on the socket | |
| 176 | 1 | this.connection.setTimeout(this.socketOptions.connectTimeoutMS != null ? this.socketOptions.connectTimeoutMS : this.socketOptions.timeout); |
| 177 | // Work around for 0.4.X | |
| 178 | 2 | if(process.version.indexOf("v0.4") == -1) this.connection.setNoDelay(this.socketOptions.noDelay); |
| 179 | // Set keep alive if defined | |
| 180 | 1 | if(process.version.indexOf("v0.4") == -1) { |
| 181 | 1 | if(this.socketOptions.keepAlive > 0) { |
| 182 | 0 | this.connection.setKeepAlive(true, this.socketOptions.keepAlive); |
| 183 | } else { | |
| 184 | 1 | this.connection.setKeepAlive(false); |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | // Set up write stream | |
| 189 | 1 | this.writeSteam = this.connection; |
| 190 | // Add handlers | |
| 191 | 1 | this.connection.on("error", errorHandler(this)); |
| 192 | // Add all handlers to the socket to manage it | |
| 193 | 1 | this.connection.on("connect", connectHandler(this)); |
| 194 | // this.connection.on("end", endHandler(this)); | |
| 195 | 1 | this.connection.on("data", createDataHandler(this)); |
| 196 | 1 | this.connection.on("timeout", timeoutHandler(this)); |
| 197 | 1 | this.connection.on("drain", drainHandler(this)); |
| 198 | 1 | this.connection.on("close", closeHandler(this)); |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | // Check if the sockets are live | |
| 203 | 1 | Connection.prototype.isConnected = function() { |
| 204 | 0 | return this.connected && !this.connection.destroyed && this.connection.writable && this.connection.readable; |
| 205 | } | |
| 206 | ||
| 207 | // Validate if the driver supports this server | |
| 208 | 1 | Connection.prototype.isCompatible = function() { |
| 209 | 0 | if(this.serverCapabilities == null) return true; |
| 210 | // Is compatible with backward server | |
| 211 | 0 | if(this.serverCapabilities.minWireVersion == 0 |
| 212 | 0 | && this.serverCapabilities.maxWireVersion ==0) return true; |
| 213 | ||
| 214 | // Check if we overlap | |
| 215 | 0 | if(this.serverCapabilities.minWireVersion >= this.minWireVersion |
| 216 | 0 | && this.serverCapabilities.maxWireVersion <= this.maxWireVersion) return true; |
| 217 | ||
| 218 | // Not compatible | |
| 219 | 0 | return false; |
| 220 | } | |
| 221 | ||
| 222 | // Write the data out to the socket | |
| 223 | 1 | Connection.prototype.write = function(command, callback) { |
| 224 | 0 | try { |
| 225 | // If we have a list off commands to be executed on the same socket | |
| 226 | 0 | if(Array.isArray(command)) { |
| 227 | 0 | for(var i = 0; i < command.length; i++) { |
| 228 | 0 | try { |
| 229 | // Pass in the bson validation settings (validate early) | |
| 230 | 0 | var binaryCommand = command[i].toBinary(this.maxBsonSettings) |
| 231 | ||
| 232 | 0 | if(this.logger != null && this.logger.doDebug) |
| 233 | 0 | this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]}); |
| 234 | ||
| 235 | 0 | this.writeSteam.write(binaryCommand); |
| 236 | } catch(err) { | |
| 237 | 0 | return callback(err, null); |
| 238 | } | |
| 239 | } | |
| 240 | } else { | |
| 241 | 0 | try { |
| 242 | // Pass in the bson validation settings (validate early) | |
| 243 | 0 | var binaryCommand = command.toBinary(this.maxBsonSettings) |
| 244 | // Do we have a logger active log the event | |
| 245 | 0 | if(this.logger != null && this.logger.doDebug) |
| 246 | 0 | this.logger.debug("writing command to mongodb", {binary: binaryCommand, json: command[i]}); |
| 247 | // Write the binary command out to socket | |
| 248 | 0 | this.writeSteam.write(binaryCommand); |
| 249 | } catch(err) { | |
| 250 | 0 | return callback(err, null) |
| 251 | } | |
| 252 | } | |
| 253 | } catch (err) { | |
| 254 | 0 | if(typeof callback === 'function') callback(err); |
| 255 | } | |
| 256 | } | |
| 257 | ||
| 258 | // Force the closure of the connection | |
| 259 | 1 | Connection.prototype.close = function() { |
| 260 | // clear out all the listeners | |
| 261 | 0 | resetHandlers(this, true); |
| 262 | // Add a dummy error listener to catch any weird last moment errors (and ignore them) | |
| 263 | 0 | this.connection.on("error", function() {}) |
| 264 | // destroy connection | |
| 265 | 0 | this.connection.destroy(); |
| 266 | 0 | if(this.logger != null && this.logger.doDebug){ |
| 267 | 0 | this.logger.debug("closed connection", this.connection); |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | // Reset all handlers | |
| 272 | 1 | var resetHandlers = function(self, clearListeners) { |
| 273 | 1 | self.eventHandlers = {error:[], connect:[], close:[], end:[], timeout:[], parseError:[], message:[]}; |
| 274 | ||
| 275 | // If we want to clear all the listeners | |
| 276 | 1 | if(clearListeners && self.connection != null) { |
| 277 | 0 | var keys = Object.keys(self.eventHandlers); |
| 278 | // Remove all listeners | |
| 279 | 0 | for(var i = 0; i < keys.length; i++) { |
| 280 | 0 | self.connection.removeAllListeners(keys[i]); |
| 281 | } | |
| 282 | } | |
| 283 | } | |
| 284 | ||
| 285 | // | |
| 286 | // Handlers | |
| 287 | // | |
| 288 | ||
| 289 | // Connect handler | |
| 290 | 1 | var connectHandler = function(self) { |
| 291 | 1 | return function(data) { |
| 292 | // Set connected | |
| 293 | 0 | self.connected = true; |
| 294 | // Now that we are connected set the socket timeout | |
| 295 | 0 | self.connection.setTimeout(self.socketOptions.socketTimeoutMS != null ? self.socketOptions.socketTimeoutMS : self.socketOptions.timeout); |
| 296 | // Emit the connect event with no error | |
| 297 | 0 | self.emit("connect", null, self); |
| 298 | } | |
| 299 | } | |
| 300 | ||
| 301 | 1 | var createDataHandler = exports.Connection.createDataHandler = function(self) { |
| 302 | // We need to handle the parsing of the data | |
| 303 | // and emit the messages when there is a complete one | |
| 304 | 1 | return function(data) { |
| 305 | // Parse until we are done with the data | |
| 306 | 0 | while(data.length > 0) { |
| 307 | // If we still have bytes to read on the current message | |
| 308 | 0 | if(self.bytesRead > 0 && self.sizeOfMessage > 0) { |
| 309 | // Calculate the amount of remaining bytes | |
| 310 | 0 | var remainingBytesToRead = self.sizeOfMessage - self.bytesRead; |
| 311 | // Check if the current chunk contains the rest of the message | |
| 312 | 0 | if(remainingBytesToRead > data.length) { |
| 313 | // Copy the new data into the exiting buffer (should have been allocated when we know the message size) | |
| 314 | 0 | data.copy(self.buffer, self.bytesRead); |
| 315 | // Adjust the number of bytes read so it point to the correct index in the buffer | |
| 316 | 0 | self.bytesRead = self.bytesRead + data.length; |
| 317 | ||
| 318 | // Reset state of buffer | |
| 319 | 0 | data = new Buffer(0); |
| 320 | } else { | |
| 321 | // Copy the missing part of the data into our current buffer | |
| 322 | 0 | data.copy(self.buffer, self.bytesRead, 0, remainingBytesToRead); |
| 323 | // Slice the overflow into a new buffer that we will then re-parse | |
| 324 | 0 | data = data.slice(remainingBytesToRead); |
| 325 | ||
| 326 | // Emit current complete message | |
| 327 | 0 | try { |
| 328 | 0 | var emitBuffer = self.buffer; |
| 329 | // Reset state of buffer | |
| 330 | 0 | self.buffer = null; |
| 331 | 0 | self.sizeOfMessage = 0; |
| 332 | 0 | self.bytesRead = 0; |
| 333 | 0 | self.stubBuffer = null; |
| 334 | // Emit the buffer | |
| 335 | 0 | self.emit("message", emitBuffer, self); |
| 336 | } catch(err) { | |
| 337 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 338 | sizeOfMessage:self.sizeOfMessage, | |
| 339 | bytesRead:self.bytesRead, | |
| 340 | stubBuffer:self.stubBuffer}}; | |
| 341 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 342 | // We got a parse Error fire it off then keep going | |
| 343 | 0 | self.emit("parseError", errorObject, self); |
| 344 | } | |
| 345 | } | |
| 346 | } else { | |
| 347 | // Stub buffer is kept in case we don't get enough bytes to determine the | |
| 348 | // size of the message (< 4 bytes) | |
| 349 | 0 | if(self.stubBuffer != null && self.stubBuffer.length > 0) { |
| 350 | ||
| 351 | // If we have enough bytes to determine the message size let's do it | |
| 352 | 0 | if(self.stubBuffer.length + data.length > 4) { |
| 353 | // Prepad the data | |
| 354 | 0 | var newData = new Buffer(self.stubBuffer.length + data.length); |
| 355 | 0 | self.stubBuffer.copy(newData, 0); |
| 356 | 0 | data.copy(newData, self.stubBuffer.length); |
| 357 | // Reassign for parsing | |
| 358 | 0 | data = newData; |
| 359 | ||
| 360 | // Reset state of buffer | |
| 361 | 0 | self.buffer = null; |
| 362 | 0 | self.sizeOfMessage = 0; |
| 363 | 0 | self.bytesRead = 0; |
| 364 | 0 | self.stubBuffer = null; |
| 365 | ||
| 366 | } else { | |
| 367 | ||
| 368 | // Add the the bytes to the stub buffer | |
| 369 | 0 | var newStubBuffer = new Buffer(self.stubBuffer.length + data.length); |
| 370 | // Copy existing stub buffer | |
| 371 | 0 | self.stubBuffer.copy(newStubBuffer, 0); |
| 372 | // Copy missing part of the data | |
| 373 | 0 | data.copy(newStubBuffer, self.stubBuffer.length); |
| 374 | // Exit parsing loop | |
| 375 | 0 | data = new Buffer(0); |
| 376 | } | |
| 377 | } else { | |
| 378 | 0 | if(data.length > 4) { |
| 379 | // Retrieve the message size | |
| 380 | 0 | var sizeOfMessage = binaryutils.decodeUInt32(data, 0); |
| 381 | // If we have a negative sizeOfMessage emit error and return | |
| 382 | 0 | if(sizeOfMessage < 0 || sizeOfMessage > self.maxBsonSize) { |
| 383 | 0 | var errorObject = {err:"socketHandler", trace:'', bin:self.buffer, parseState:{ |
| 384 | sizeOfMessage: sizeOfMessage, | |
| 385 | bytesRead: self.bytesRead, | |
| 386 | stubBuffer: self.stubBuffer}}; | |
| 387 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 388 | // We got a parse Error fire it off then keep going | |
| 389 | 0 | self.emit("parseError", errorObject, self); |
| 390 | 0 | return; |
| 391 | } | |
| 392 | ||
| 393 | // Ensure that the size of message is larger than 0 and less than the max allowed | |
| 394 | 0 | if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage > data.length) { |
| 395 | 0 | self.buffer = new Buffer(sizeOfMessage); |
| 396 | // Copy all the data into the buffer | |
| 397 | 0 | data.copy(self.buffer, 0); |
| 398 | // Update bytes read | |
| 399 | 0 | self.bytesRead = data.length; |
| 400 | // Update sizeOfMessage | |
| 401 | 0 | self.sizeOfMessage = sizeOfMessage; |
| 402 | // Ensure stub buffer is null | |
| 403 | 0 | self.stubBuffer = null; |
| 404 | // Exit parsing loop | |
| 405 | 0 | data = new Buffer(0); |
| 406 | ||
| 407 | 0 | } else if(sizeOfMessage > 4 && sizeOfMessage < self.maxBsonSize && sizeOfMessage == data.length) { |
| 408 | 0 | try { |
| 409 | 0 | var emitBuffer = data; |
| 410 | // Reset state of buffer | |
| 411 | 0 | self.buffer = null; |
| 412 | 0 | self.sizeOfMessage = 0; |
| 413 | 0 | self.bytesRead = 0; |
| 414 | 0 | self.stubBuffer = null; |
| 415 | // Exit parsing loop | |
| 416 | 0 | data = new Buffer(0); |
| 417 | // Emit the message | |
| 418 | 0 | self.emit("message", emitBuffer, self); |
| 419 | } catch (err) { | |
| 420 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 421 | sizeOfMessage:self.sizeOfMessage, | |
| 422 | bytesRead:self.bytesRead, | |
| 423 | stubBuffer:self.stubBuffer}}; | |
| 424 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 425 | // We got a parse Error fire it off then keep going | |
| 426 | 0 | self.emit("parseError", errorObject, self); |
| 427 | } | |
| 428 | 0 | } else if(sizeOfMessage <= 4 || sizeOfMessage > self.maxBsonSize) { |
| 429 | 0 | var errorObject = {err:"socketHandler", trace:null, bin:data, parseState:{ |
| 430 | sizeOfMessage:sizeOfMessage, | |
| 431 | bytesRead:0, | |
| 432 | buffer:null, | |
| 433 | stubBuffer:null}}; | |
| 434 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 435 | // We got a parse Error fire it off then keep going | |
| 436 | 0 | self.emit("parseError", errorObject, self); |
| 437 | ||
| 438 | // Clear out the state of the parser | |
| 439 | 0 | self.buffer = null; |
| 440 | 0 | self.sizeOfMessage = 0; |
| 441 | 0 | self.bytesRead = 0; |
| 442 | 0 | self.stubBuffer = null; |
| 443 | // Exit parsing loop | |
| 444 | 0 | data = new Buffer(0); |
| 445 | ||
| 446 | } else { | |
| 447 | 0 | try { |
| 448 | 0 | var emitBuffer = data.slice(0, sizeOfMessage); |
| 449 | // Reset state of buffer | |
| 450 | 0 | self.buffer = null; |
| 451 | 0 | self.sizeOfMessage = 0; |
| 452 | 0 | self.bytesRead = 0; |
| 453 | 0 | self.stubBuffer = null; |
| 454 | // Copy rest of message | |
| 455 | 0 | data = data.slice(sizeOfMessage); |
| 456 | // Emit the message | |
| 457 | 0 | self.emit("message", emitBuffer, self); |
| 458 | } catch (err) { | |
| 459 | 0 | var errorObject = {err:"socketHandler", trace:err, bin:self.buffer, parseState:{ |
| 460 | sizeOfMessage:sizeOfMessage, | |
| 461 | bytesRead:self.bytesRead, | |
| 462 | stubBuffer:self.stubBuffer}}; | |
| 463 | 0 | if(self.logger != null && self.logger.doError) self.logger.error("parseError", errorObject); |
| 464 | // We got a parse Error fire it off then keep going | |
| 465 | 0 | self.emit("parseError", errorObject, self); |
| 466 | } | |
| 467 | ||
| 468 | } | |
| 469 | } else { | |
| 470 | // Create a buffer that contains the space for the non-complete message | |
| 471 | 0 | self.stubBuffer = new Buffer(data.length) |
| 472 | // Copy the data to the stub buffer | |
| 473 | 0 | data.copy(self.stubBuffer, 0); |
| 474 | // Exit parsing loop | |
| 475 | 0 | data = new Buffer(0); |
| 476 | } | |
| 477 | } | |
| 478 | } | |
| 479 | } | |
| 480 | } | |
| 481 | } | |
| 482 | ||
| 483 | 1 | var endHandler = function(self) { |
| 484 | 0 | return function() { |
| 485 | // Set connected to false | |
| 486 | 0 | self.connected = false; |
| 487 | // Emit end event | |
| 488 | 0 | self.emit("end", {err: 'connection received Fin packet from [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 489 | } | |
| 490 | } | |
| 491 | ||
| 492 | 1 | var timeoutHandler = function(self) { |
| 493 | 1 | return function() { |
| 494 | // Set connected to false | |
| 495 | 0 | self.connected = false; |
| 496 | // Emit timeout event | |
| 497 | 0 | self.emit("timeout", {err: 'connection to [' + self.socketOptions.host + ':' + self.socketOptions.port + '] timed out'}, self); |
| 498 | } | |
| 499 | } | |
| 500 | ||
| 501 | 1 | var drainHandler = function(self) { |
| 502 | 1 | return function() { |
| 503 | } | |
| 504 | } | |
| 505 | ||
| 506 | 1 | var errorHandler = function(self) { |
| 507 | 1 | return function(err) { |
| 508 | 0 | self.connection.destroy(); |
| 509 | // Set connected to false | |
| 510 | 0 | self.connected = false; |
| 511 | // Emit error | |
| 512 | 0 | self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 513 | } | |
| 514 | } | |
| 515 | ||
| 516 | 1 | var closeHandler = function(self) { |
| 517 | 1 | return function(hadError) { |
| 518 | // If we have an error during the connection phase | |
| 519 | 0 | if(hadError && !self.connected) { |
| 520 | // Set disconnected | |
| 521 | 0 | self.connected = false; |
| 522 | // Emit error | |
| 523 | 0 | self.emit("error", {err: 'failed to connect to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 524 | } else { | |
| 525 | // Set disconnected | |
| 526 | 0 | self.connected = false; |
| 527 | // Emit close | |
| 528 | 0 | self.emit("close", {err: 'connection closed to [' + self.socketOptions.host + ':' + self.socketOptions.port + ']'}, self); |
| 529 | } | |
| 530 | } | |
| 531 | } | |
| 532 | ||
| 533 | // Some basic defaults | |
| 534 | 1 | Connection.DEFAULT_PORT = 27017; |
| 535 | ||
| 536 | ||
| 537 | ||
| 538 | ||
| 539 | ||
| 540 | ||
| 541 | ||
| 542 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var utils = require('./connection_utils'), |
| 2 | inherits = require('util').inherits, | |
| 3 | net = require('net'), | |
| 4 | timers = require('timers'), | |
| 5 | EventEmitter = require('events').EventEmitter, | |
| 6 | inherits = require('util').inherits, | |
| 7 | MongoReply = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/../responses/mongo_reply").MongoReply, | |
| 8 | Connection = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/./connection").Connection; | |
| 9 | ||
| 10 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 11 | 1 | var processor = require('../utils').processor(); |
| 12 | ||
| 13 | 1 | var ConnectionPool = exports.ConnectionPool = function(host, port, poolSize, bson, socketOptions) { |
| 14 | 1 | if(typeof host !== 'string') { |
| 15 | 0 | throw new Error("host must be specified [" + host + "]"); |
| 16 | } | |
| 17 | ||
| 18 | // Set up event emitter | |
| 19 | 1 | EventEmitter.call(this); |
| 20 | ||
| 21 | // Keep all options for the socket in a specific collection allowing the user to specify the | |
| 22 | // Wished upon socket connection parameters | |
| 23 | 1 | this.socketOptions = typeof socketOptions === 'object' ? socketOptions : {}; |
| 24 | 1 | this.socketOptions.host = host; |
| 25 | 1 | this.socketOptions.port = port; |
| 26 | 1 | this.socketOptions.domainSocket = false; |
| 27 | 1 | this.bson = bson; |
| 28 | // PoolSize is always + 1 for special reserved "measurment" socket (like ping, stats etc) | |
| 29 | 1 | this.poolSize = poolSize; |
| 30 | 1 | this.minPoolSize = Math.floor(this.poolSize / 2) + 1; |
| 31 | ||
| 32 | // Check if the host is a socket | |
| 33 | 1 | if(host.match(/^\//)) { |
| 34 | 0 | this.socketOptions.domainSocket = true; |
| 35 | 1 | } else if(typeof port === 'string') { |
| 36 | 0 | try { |
| 37 | 0 | port = parseInt(port, 10); |
| 38 | } catch(err) { | |
| 39 | 0 | new Error("port must be specified or valid integer[" + port + "]"); |
| 40 | } | |
| 41 | 1 | } else if(typeof port !== 'number') { |
| 42 | 0 | throw new Error("port must be specified [" + port + "]"); |
| 43 | } | |
| 44 | ||
| 45 | // Set default settings for the socket options | |
| 46 | 1 | utils.setIntegerParameter(this.socketOptions, 'timeout', 0); |
| 47 | // Delay before writing out the data to the server | |
| 48 | 1 | utils.setBooleanParameter(this.socketOptions, 'noDelay', true); |
| 49 | // Delay before writing out the data to the server | |
| 50 | 1 | utils.setIntegerParameter(this.socketOptions, 'keepAlive', 0); |
| 51 | // Set the encoding of the data read, default is binary == null | |
| 52 | 1 | utils.setStringParameter(this.socketOptions, 'encoding', null); |
| 53 | // Allows you to set a throttling bufferSize if you need to stop overflows | |
| 54 | 1 | utils.setIntegerParameter(this.socketOptions, 'bufferSize', 0); |
| 55 | ||
| 56 | // Internal structures | |
| 57 | 1 | this.openConnections = []; |
| 58 | // Assign connection id's | |
| 59 | 1 | this.connectionId = 0; |
| 60 | ||
| 61 | // Current index for selection of pool connection | |
| 62 | 1 | this.currentConnectionIndex = 0; |
| 63 | // The pool state | |
| 64 | 1 | this._poolState = 'disconnected'; |
| 65 | // timeout control | |
| 66 | 1 | this._timeout = false; |
| 67 | // Time to wait between connections for the pool | |
| 68 | 1 | this._timeToWait = 10; |
| 69 | } | |
| 70 | ||
| 71 | 1 | inherits(ConnectionPool, EventEmitter); |
| 72 | ||
| 73 | 1 | ConnectionPool.prototype.setMaxBsonSize = function(maxBsonSize) { |
| 74 | 0 | if(maxBsonSize == null){ |
| 75 | 0 | maxBsonSize = Connection.DEFAULT_MAX_BSON_SIZE; |
| 76 | } | |
| 77 | ||
| 78 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 79 | 0 | this.openConnections[i].maxBsonSize = maxBsonSize; |
| 80 | 0 | this.openConnections[i].maxBsonSettings.maxBsonSize = maxBsonSize; |
| 81 | } | |
| 82 | } | |
| 83 | ||
| 84 | 1 | ConnectionPool.prototype.setMaxMessageSizeBytes = function(maxMessageSizeBytes) { |
| 85 | 0 | if(maxMessageSizeBytes == null){ |
| 86 | 0 | maxMessageSizeBytes = Connection.DEFAULT_MAX_MESSAGE_SIZE; |
| 87 | } | |
| 88 | ||
| 89 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 90 | 0 | this.openConnections[i].maxMessageSizeBytes = maxMessageSizeBytes; |
| 91 | 0 | this.openConnections[i].maxBsonSettings.maxMessageSizeBytes = maxMessageSizeBytes; |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | // Start a function | |
| 96 | 1 | var _connect = function(_self) { |
| 97 | // return new function() { | |
| 98 | // Create a new connection instance | |
| 99 | 1 | var connection = new Connection(_self.connectionId++, _self.socketOptions); |
| 100 | // Set logger on pool | |
| 101 | 1 | connection.logger = _self.logger; |
| 102 | // Connect handler | |
| 103 | 1 | connection.on("connect", function(err, connection) { |
| 104 | // Add connection to list of open connections | |
| 105 | 0 | _self.openConnections.push(connection); |
| 106 | // If the number of open connections is equal to the poolSize signal ready pool | |
| 107 | 0 | if(_self.openConnections.length === _self.poolSize && _self._poolState !== 'disconnected') { |
| 108 | // Set connected | |
| 109 | 0 | _self._poolState = 'connected'; |
| 110 | // Emit pool ready | |
| 111 | 0 | _self.emit("poolReady"); |
| 112 | 0 | } else if(_self.openConnections.length < _self.poolSize) { |
| 113 | // Wait a little bit of time to let the close event happen if the server closes the connection | |
| 114 | // so we don't leave hanging connections around | |
| 115 | 0 | if(typeof _self._timeToWait == 'number') { |
| 116 | 0 | setTimeout(function() { |
| 117 | // If we are still connecting (no close events fired in between start another connection) | |
| 118 | 0 | if(_self._poolState == 'connecting') { |
| 119 | 0 | _connect(_self); |
| 120 | } | |
| 121 | }, _self._timeToWait); | |
| 122 | } else { | |
| 123 | 0 | processor(function() { |
| 124 | // If we are still connecting (no close events fired in between start another connection) | |
| 125 | 0 | if(_self._poolState == 'connecting') { |
| 126 | 0 | _connect(_self); |
| 127 | } | |
| 128 | }); | |
| 129 | } | |
| 130 | } | |
| 131 | }); | |
| 132 | ||
| 133 | 1 | var numberOfErrors = 0 |
| 134 | ||
| 135 | // Error handler | |
| 136 | 1 | connection.on("error", function(err, connection, error_options) { |
| 137 | 0 | numberOfErrors++; |
| 138 | // If we are already disconnected ignore the event | |
| 139 | 0 | if(_self._poolState != 'disconnected' && _self.listeners("error").length > 0) { |
| 140 | 0 | _self.emit("error", err, connection, error_options); |
| 141 | } | |
| 142 | ||
| 143 | // Close the connection | |
| 144 | 0 | connection.close(); |
| 145 | // Set pool as disconnected | |
| 146 | 0 | _self._poolState = 'disconnected'; |
| 147 | // Stop the pool | |
| 148 | 0 | _self.stop(); |
| 149 | }); | |
| 150 | ||
| 151 | // Close handler | |
| 152 | 1 | connection.on("close", function() { |
| 153 | // If we are already disconnected ignore the event | |
| 154 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("close").length > 0) { |
| 155 | 0 | _self.emit("close"); |
| 156 | } | |
| 157 | ||
| 158 | // Set disconnected | |
| 159 | 0 | _self._poolState = 'disconnected'; |
| 160 | // Stop | |
| 161 | 0 | _self.stop(); |
| 162 | }); | |
| 163 | ||
| 164 | // Timeout handler | |
| 165 | 1 | connection.on("timeout", function(err, connection) { |
| 166 | // If we are already disconnected ignore the event | |
| 167 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("timeout").length > 0) { |
| 168 | 0 | _self.emit("timeout", err); |
| 169 | } | |
| 170 | ||
| 171 | // Close the connection | |
| 172 | 0 | connection.close(); |
| 173 | // Set disconnected | |
| 174 | 0 | _self._poolState = 'disconnected'; |
| 175 | 0 | _self.stop(); |
| 176 | }); | |
| 177 | ||
| 178 | // Parse error, needs a complete shutdown of the pool | |
| 179 | 1 | connection.on("parseError", function() { |
| 180 | // If we are already disconnected ignore the event | |
| 181 | 0 | if(_self._poolState !== 'disconnected' && _self.listeners("parseError").length > 0) { |
| 182 | 0 | _self.emit("parseError", new Error("parseError occured")); |
| 183 | } | |
| 184 | ||
| 185 | // Set disconnected | |
| 186 | 0 | _self._poolState = 'disconnected'; |
| 187 | 0 | _self.stop(); |
| 188 | }); | |
| 189 | ||
| 190 | 1 | connection.on("message", function(message) { |
| 191 | 0 | _self.emit("message", message); |
| 192 | }); | |
| 193 | ||
| 194 | // Start connection in the next tick | |
| 195 | 1 | connection.start(); |
| 196 | // }(); | |
| 197 | } | |
| 198 | ||
| 199 | ||
| 200 | // Start method, will throw error if no listeners are available | |
| 201 | // Pass in an instance of the listener that contains the api for | |
| 202 | // finding callbacks for a given message etc. | |
| 203 | 1 | ConnectionPool.prototype.start = function() { |
| 204 | 1 | var markerDate = new Date().getTime(); |
| 205 | 1 | var self = this; |
| 206 | ||
| 207 | 1 | if(this.listeners("poolReady").length == 0) { |
| 208 | 0 | throw "pool must have at least one listener ready that responds to the [poolReady] event"; |
| 209 | } | |
| 210 | ||
| 211 | // Set pool state to connecting | |
| 212 | 1 | this._poolState = 'connecting'; |
| 213 | 1 | this._timeout = false; |
| 214 | ||
| 215 | 1 | _connect(self); |
| 216 | } | |
| 217 | ||
| 218 | // Restart a connection pool (on a close the pool might be in a wrong state) | |
| 219 | 1 | ConnectionPool.prototype.restart = function() { |
| 220 | // Close all connections | |
| 221 | 0 | this.stop(false); |
| 222 | // Now restart the pool | |
| 223 | 0 | this.start(); |
| 224 | } | |
| 225 | ||
| 226 | // Stop the connections in the pool | |
| 227 | 1 | ConnectionPool.prototype.stop = function(removeListeners) { |
| 228 | 0 | removeListeners = removeListeners == null ? true : removeListeners; |
| 229 | // Set disconnected | |
| 230 | 0 | this._poolState = 'disconnected'; |
| 231 | ||
| 232 | // Clear all listeners if specified | |
| 233 | 0 | if(removeListeners) { |
| 234 | 0 | this.removeAllEventListeners(); |
| 235 | } | |
| 236 | ||
| 237 | // Close all connections | |
| 238 | 0 | for(var i = 0; i < this.openConnections.length; i++) { |
| 239 | 0 | this.openConnections[i].close(); |
| 240 | } | |
| 241 | ||
| 242 | // Clean up | |
| 243 | 0 | this.openConnections = []; |
| 244 | } | |
| 245 | ||
| 246 | // Check the status of the connection | |
| 247 | 1 | ConnectionPool.prototype.isConnected = function() { |
| 248 | // return this._poolState === 'connected'; | |
| 249 | 0 | return this.openConnections.length > 0 && this.openConnections[0].isConnected(); |
| 250 | } | |
| 251 | ||
| 252 | // Checkout a connection from the pool for usage, or grab a specific pool instance | |
| 253 | 1 | ConnectionPool.prototype.checkoutConnection = function(id) { |
| 254 | 0 | var index = (this.currentConnectionIndex++ % (this.openConnections.length)); |
| 255 | 0 | var connection = this.openConnections[index]; |
| 256 | 0 | return connection; |
| 257 | } | |
| 258 | ||
| 259 | 1 | ConnectionPool.prototype.getAllConnections = function() { |
| 260 | 0 | return this.openConnections; |
| 261 | } | |
| 262 | ||
| 263 | // Remove all non-needed event listeners | |
| 264 | 1 | ConnectionPool.prototype.removeAllEventListeners = function() { |
| 265 | 0 | this.removeAllListeners("close"); |
| 266 | 0 | this.removeAllListeners("error"); |
| 267 | 0 | this.removeAllListeners("timeout"); |
| 268 | 0 | this.removeAllListeners("connect"); |
| 269 | 0 | this.removeAllListeners("end"); |
| 270 | 0 | this.removeAllListeners("parseError"); |
| 271 | 0 | this.removeAllListeners("message"); |
| 272 | 0 | this.removeAllListeners("poolReady"); |
| 273 | } | |
| 274 | ||
| 275 | ||
| 276 | ||
| 277 | ||
| 278 | ||
| 279 | ||
| 280 | ||
| 281 | ||
| 282 | ||
| 283 | ||
| 284 | ||
| 285 | ||
| 286 | ||
| 287 | ||
| 288 | ||
| 289 | ||
| 290 | ||
| 291 | ||
| 292 | ||
| 293 | ||
| 294 | ||
| 295 | ||
| 296 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | exports.setIntegerParameter = function(object, field, defaultValue) { |
| 2 | 3 | if(object[field] == null) { |
| 3 | 3 | object[field] = defaultValue; |
| 4 | 0 | } else if(typeof object[field] !== "number" && object[field] !== parseInt(object[field], 10)) { |
| 5 | 0 | throw "object field [" + field + "] must be a numeric integer value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 6 | } | |
| 7 | } | |
| 8 | ||
| 9 | 1 | exports.setBooleanParameter = function(object, field, defaultValue) { |
| 10 | 1 | if(object[field] == null) { |
| 11 | 1 | object[field] = defaultValue; |
| 12 | 0 | } else if(typeof object[field] !== "boolean") { |
| 13 | 0 | throw "object field [" + field + "] must be a boolean value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 14 | } | |
| 15 | } | |
| 16 | ||
| 17 | 1 | exports.setStringParameter = function(object, field, defaultValue) { |
| 18 | 1 | if(object[field] == null) { |
| 19 | 1 | object[field] = defaultValue; |
| 20 | 0 | } else if(typeof object[field] !== "string") { |
| 21 | 0 | throw "object field [" + field + "] must be a string value, attempted to set to [" + object[field] + "] type of [" + typeof object[field] + "]"; |
| 22 | } | |
| 23 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('./read_preference').ReadPreference |
| 2 | , Base = require('./base').Base | |
| 3 | , Server = require('./server').Server | |
| 4 | , format = require('util').format | |
| 5 | , timers = require('timers') | |
| 6 | , utils = require('../utils') | |
| 7 | , inherits = require('util').inherits; | |
| 8 | ||
| 9 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 10 | 1 | var processor = require('../utils').processor(); |
| 11 | ||
| 12 | /** | |
| 13 | * Mongos constructor provides a connection to a mongos proxy including failover to additional servers | |
| 14 | * | |
| 15 | * Options | |
| 16 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 17 | * - **ha** {Boolean, default:true}, turn on high availability, attempts to reconnect to down proxies | |
| 18 | * - **haInterval** {Number, default:2000}, time between each replicaset status check. | |
| 19 | * | |
| 20 | * @class Represents a Mongos connection with failover to backup proxies | |
| 21 | * @param {Array} list of mongos server objects | |
| 22 | * @param {Object} [options] additional options for the mongos connection | |
| 23 | */ | |
| 24 | 1 | var Mongos = function Mongos(servers, options) { |
| 25 | // Set up basic | |
| 26 | 0 | if(!(this instanceof Mongos)) |
| 27 | 0 | return new Mongos(servers, options); |
| 28 | ||
| 29 | // Set up event emitter | |
| 30 | 0 | Base.call(this); |
| 31 | ||
| 32 | // Throw error on wrong setup | |
| 33 | 0 | if(servers == null || !Array.isArray(servers) || servers.length == 0) |
| 34 | 0 | throw new Error("At least one mongos proxy must be in the array"); |
| 35 | ||
| 36 | // Ensure we have at least an empty options object | |
| 37 | 0 | this.options = options == null ? {} : options; |
| 38 | // Set default connection pool options | |
| 39 | 0 | this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; |
| 40 | // Enabled ha | |
| 41 | 0 | this.haEnabled = this.options['ha'] == null ? true : this.options['ha']; |
| 42 | 0 | this._haInProgress = false; |
| 43 | // How often are we checking for new servers in the replicaset | |
| 44 | 0 | this.mongosStatusCheckInterval = this.options['haInterval'] == null ? 1000 : this.options['haInterval']; |
| 45 | // Save all the server connections | |
| 46 | 0 | this.servers = servers; |
| 47 | // Servers we need to attempt reconnect with | |
| 48 | 0 | this.downServers = {}; |
| 49 | // Servers that are up | |
| 50 | 0 | this.upServers = {}; |
| 51 | // Up servers by ping time | |
| 52 | 0 | this.upServersByUpTime = {}; |
| 53 | // Emit open setup | |
| 54 | 0 | this.emitOpen = this.options.emitOpen || true; |
| 55 | // Just contains the current lowest ping time and server | |
| 56 | 0 | this.lowestPingTimeServer = null; |
| 57 | 0 | this.lowestPingTime = 0; |
| 58 | // Connection timeout | |
| 59 | 0 | this._connectTimeoutMS = this.socketOptions.connectTimeoutMS |
| 60 | ? this.socketOptions.connectTimeoutMS | |
| 61 | : 1000; | |
| 62 | ||
| 63 | // Add options to servers | |
| 64 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 65 | 0 | var server = this.servers[i]; |
| 66 | 0 | server._callBackStore = this._callBackStore; |
| 67 | 0 | server.auto_reconnect = false; |
| 68 | // Default empty socket options object | |
| 69 | 0 | var socketOptions = {host: server.host, port: server.port}; |
| 70 | // If a socket option object exists clone it | |
| 71 | 0 | if(this.socketOptions != null) { |
| 72 | 0 | var keys = Object.keys(this.socketOptions); |
| 73 | 0 | for(var k = 0; k < keys.length;k++) socketOptions[keys[i]] = this.socketOptions[keys[i]]; |
| 74 | } | |
| 75 | ||
| 76 | // Set socket options | |
| 77 | 0 | server.socketOptions = socketOptions; |
| 78 | } | |
| 79 | ||
| 80 | // Allow setting the socketTimeoutMS on all connections | |
| 81 | // to work around issues such as secondaries blocking due to compaction | |
| 82 | 0 | utils.setSocketTimeoutProperty(this, this.socketOptions); |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * @ignore | |
| 87 | */ | |
| 88 | 1 | inherits(Mongos, Base); |
| 89 | ||
| 90 | /** | |
| 91 | * @ignore | |
| 92 | */ | |
| 93 | 1 | Mongos.prototype.isMongos = function() { |
| 94 | 0 | return true; |
| 95 | } | |
| 96 | ||
| 97 | /** | |
| 98 | * @ignore | |
| 99 | */ | |
| 100 | 1 | Mongos.prototype.connect = function(db, options, callback) { |
| 101 | 0 | if('function' === typeof options) callback = options, options = {}; |
| 102 | 0 | if(options == null) options = {}; |
| 103 | 0 | if(!('function' === typeof callback)) callback = null; |
| 104 | 0 | var self = this; |
| 105 | ||
| 106 | // Keep reference to parent | |
| 107 | 0 | this.db = db; |
| 108 | // Set server state to connecting | |
| 109 | 0 | this._serverState = 'connecting'; |
| 110 | // Number of total servers that need to initialized (known servers) | |
| 111 | 0 | this._numberOfServersLeftToInitialize = this.servers.length; |
| 112 | // Connect handler | |
| 113 | 0 | var connectHandler = function(_server) { |
| 114 | 0 | return function(err, result) { |
| 115 | 0 | self._numberOfServersLeftToInitialize = self._numberOfServersLeftToInitialize - 1; |
| 116 | ||
| 117 | // Add the server to the list of servers that are up | |
| 118 | 0 | if(!err) { |
| 119 | 0 | self.upServers[format("%s:%s", _server.host, _server.port)] = _server; |
| 120 | } | |
| 121 | ||
| 122 | // We are done connecting | |
| 123 | 0 | if(self._numberOfServersLeftToInitialize == 0) { |
| 124 | // Start ha function if it exists | |
| 125 | 0 | if(self.haEnabled) { |
| 126 | // Setup the ha process | |
| 127 | 0 | if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId); |
| 128 | 0 | self._replicasetTimeoutId = setInterval(self.mongosCheckFunction, self.mongosStatusCheckInterval); |
| 129 | } | |
| 130 | ||
| 131 | // Set the mongos to connected | |
| 132 | 0 | self._serverState = "connected"; |
| 133 | ||
| 134 | // Emit the open event | |
| 135 | 0 | if(self.emitOpen) |
| 136 | 0 | self._emitAcrossAllDbInstances(self, null, "open", null, null, null); |
| 137 | ||
| 138 | 0 | self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null); |
| 139 | // Callback | |
| 140 | 0 | callback(null, self.db); |
| 141 | } | |
| 142 | } | |
| 143 | }; | |
| 144 | ||
| 145 | // Error handler | |
| 146 | 0 | var errorOrCloseHandler = function(_server) { |
| 147 | 0 | return function(err, result) { |
| 148 | // Emit left event, signaling mongos left the ha | |
| 149 | 0 | self.emit('left', 'mongos', _server); |
| 150 | // Execute all the callbacks with errors | |
| 151 | 0 | self.__executeAllCallbacksWithError(err); |
| 152 | // Check if we have the server | |
| 153 | 0 | var found = false; |
| 154 | ||
| 155 | // Get the server name | |
| 156 | 0 | var server_name = format("%s:%s", _server.host, _server.port); |
| 157 | // Add the downed server | |
| 158 | 0 | self.downServers[server_name] = _server; |
| 159 | // Remove the current server from the list | |
| 160 | 0 | delete self.upServers[server_name]; |
| 161 | ||
| 162 | // Emit close across all the attached db instances | |
| 163 | 0 | if(Object.keys(self.upServers).length == 0) { |
| 164 | 0 | self._emitAcrossAllDbInstances(self, null, "close", new Error("mongos disconnected, no valid proxies contactable over tcp"), null, null); |
| 165 | } | |
| 166 | } | |
| 167 | } | |
| 168 | ||
| 169 | // Mongo function | |
| 170 | 0 | this.mongosCheckFunction = function() { |
| 171 | // Set as not waiting for check event | |
| 172 | 0 | self._haInProgress = true; |
| 173 | ||
| 174 | // Servers down | |
| 175 | 0 | var numberOfServersLeft = Object.keys(self.downServers).length; |
| 176 | ||
| 177 | // Check downed servers | |
| 178 | 0 | if(numberOfServersLeft > 0) { |
| 179 | 0 | for(var name in self.downServers) { |
| 180 | // Pop a downed server | |
| 181 | 0 | var downServer = self.downServers[name]; |
| 182 | // Set up the connection options for a Mongos | |
| 183 | 0 | var options = { |
| 184 | auto_reconnect: false, | |
| 185 | returnIsMasterResults: true, | |
| 186 | slaveOk: true, | |
| 187 | poolSize: downServer.poolSize, | |
| 188 | socketOptions: { | |
| 189 | connectTimeoutMS: self._connectTimeoutMS, | |
| 190 | socketTimeoutMS: self._socketTimeoutMS | |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | // Create a new server object | |
| 195 | 0 | var newServer = new Server(downServer.host, downServer.port, options); |
| 196 | // Setup the connection function | |
| 197 | 0 | var connectFunction = function(_db, _server, _options, _callback) { |
| 198 | 0 | return function() { |
| 199 | // Attempt to connect | |
| 200 | 0 | _server.connect(_db, _options, function(err, result) { |
| 201 | 0 | numberOfServersLeft = numberOfServersLeft - 1; |
| 202 | ||
| 203 | 0 | if(err) { |
| 204 | 0 | return _callback(err, _server); |
| 205 | } else { | |
| 206 | // Set the new server settings | |
| 207 | 0 | _server._callBackStore = self._callBackStore; |
| 208 | ||
| 209 | // Add server event handlers | |
| 210 | 0 | _server.on("close", errorOrCloseHandler(_server)); |
| 211 | 0 | _server.on("timeout", errorOrCloseHandler(_server)); |
| 212 | 0 | _server.on("error", errorOrCloseHandler(_server)); |
| 213 | ||
| 214 | // Get a read connection | |
| 215 | 0 | var _connection = _server.checkoutReader(); |
| 216 | // Get the start time | |
| 217 | 0 | var startTime = new Date().getTime(); |
| 218 | ||
| 219 | // Execute ping command to mark each server with the expected times | |
| 220 | 0 | self.db.command({ping:1} |
| 221 | , {failFast:true, connection:_connection}, function(err, result) { | |
| 222 | // Get the start time | |
| 223 | 0 | var endTime = new Date().getTime(); |
| 224 | // Mark the server with the ping time | |
| 225 | 0 | _server.runtimeStats['pingMs'] = endTime - startTime; |
| 226 | // Execute any waiting reads | |
| 227 | 0 | self._commandsStore.execute_writes(); |
| 228 | 0 | self._commandsStore.execute_queries(); |
| 229 | // Callback | |
| 230 | 0 | return _callback(null, _server); |
| 231 | }); | |
| 232 | } | |
| 233 | }); | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | // Attempt to connect to the database | |
| 238 | 0 | connectFunction(self.db, newServer, options, function(err, _server) { |
| 239 | // If we have an error | |
| 240 | 0 | if(err) { |
| 241 | 0 | self.downServers[format("%s:%s", _server.host, _server.port)] = _server; |
| 242 | } | |
| 243 | ||
| 244 | // Connection function | |
| 245 | 0 | var connectionFunction = function(_auth, _connection, _callback) { |
| 246 | 0 | var pending = _auth.length(); |
| 247 | ||
| 248 | 0 | for(var j = 0; j < pending; j++) { |
| 249 | // Get the auth object | |
| 250 | 0 | var _auth = _auth.get(j); |
| 251 | // Unpack the parameter | |
| 252 | 0 | var username = _auth.username; |
| 253 | 0 | var password = _auth.password; |
| 254 | 0 | var options = { |
| 255 | authMechanism: _auth.authMechanism | |
| 256 | , authSource: _auth.authdb | |
| 257 | , connection: _connection | |
| 258 | }; | |
| 259 | ||
| 260 | // If we have changed the service name | |
| 261 | 0 | if(_auth.gssapiServiceName) |
| 262 | 0 | options.gssapiServiceName = _auth.gssapiServiceName; |
| 263 | ||
| 264 | // Hold any error | |
| 265 | 0 | var _error = null; |
| 266 | // Authenticate against the credentials | |
| 267 | 0 | self.db.authenticate(username, password, options, function(err, result) { |
| 268 | 0 | _error = err != null ? err : _error; |
| 269 | // Adjust the pending authentication | |
| 270 | 0 | pending = pending - 1; |
| 271 | // Finished up | |
| 272 | 0 | if(pending == 0) _callback(_error ? _error : null, _error ? false : true); |
| 273 | }); | |
| 274 | } | |
| 275 | } | |
| 276 | ||
| 277 | // Run auths against the connections | |
| 278 | 0 | if(self.auth.length() > 0) { |
| 279 | 0 | var connections = _server.allRawConnections(); |
| 280 | 0 | var pendingAuthConn = connections.length; |
| 281 | ||
| 282 | // No connections we are done | |
| 283 | 0 | if(connections.length == 0) { |
| 284 | // Set ha done | |
| 285 | 0 | if(numberOfServersLeft == 0) { |
| 286 | 0 | self._haInProgress = false; |
| 287 | } | |
| 288 | } | |
| 289 | ||
| 290 | // Final error object | |
| 291 | 0 | var finalError = null; |
| 292 | // Go over all the connections | |
| 293 | 0 | for(var j = 0; j < connections.length; j++) { |
| 294 | ||
| 295 | // Execute against all the connections | |
| 296 | 0 | connectionFunction(self.auth, connections[j], function(err, result) { |
| 297 | // Pending authentication | |
| 298 | 0 | pendingAuthConn = pendingAuthConn - 1 ; |
| 299 | ||
| 300 | // Save error if any | |
| 301 | 0 | finalError = err ? err : finalError; |
| 302 | ||
| 303 | // If we are done let's finish up | |
| 304 | 0 | if(pendingAuthConn == 0) { |
| 305 | // Set ha done | |
| 306 | 0 | if(numberOfServersLeft == 0) { |
| 307 | 0 | self._haInProgress = false; |
| 308 | } | |
| 309 | ||
| 310 | 0 | if(!err) { |
| 311 | 0 | add_server(self, _server); |
| 312 | } | |
| 313 | ||
| 314 | // Execute any waiting reads | |
| 315 | 0 | self._commandsStore.execute_writes(); |
| 316 | 0 | self._commandsStore.execute_queries(); |
| 317 | } | |
| 318 | }); | |
| 319 | } | |
| 320 | } else { | |
| 321 | 0 | if(!err) { |
| 322 | 0 | add_server(self, _server); |
| 323 | } | |
| 324 | ||
| 325 | // Set ha done | |
| 326 | 0 | if(numberOfServersLeft == 0) { |
| 327 | 0 | self._haInProgress = false; |
| 328 | // Execute any waiting reads | |
| 329 | 0 | self._commandsStore.execute_writes(); |
| 330 | 0 | self._commandsStore.execute_queries(); |
| 331 | } | |
| 332 | } | |
| 333 | })(); | |
| 334 | } | |
| 335 | } else { | |
| 336 | 0 | self._haInProgress = false; |
| 337 | } | |
| 338 | } | |
| 339 | ||
| 340 | // Connect all the server instances | |
| 341 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 342 | // Get the connection | |
| 343 | 0 | var server = this.servers[i]; |
| 344 | 0 | server.mongosInstance = this; |
| 345 | // Add server event handlers | |
| 346 | 0 | server.on("close", errorOrCloseHandler(server)); |
| 347 | 0 | server.on("timeout", errorOrCloseHandler(server)); |
| 348 | 0 | server.on("error", errorOrCloseHandler(server)); |
| 349 | ||
| 350 | // Configuration | |
| 351 | 0 | var options = { |
| 352 | slaveOk: true, | |
| 353 | poolSize: server.poolSize, | |
| 354 | socketOptions: { connectTimeoutMS: self._connectTimeoutMS }, | |
| 355 | returnIsMasterResults: true | |
| 356 | } | |
| 357 | ||
| 358 | // Connect the instance | |
| 359 | 0 | server.connect(self.db, options, connectHandler(server)); |
| 360 | } | |
| 361 | } | |
| 362 | ||
| 363 | /** | |
| 364 | * @ignore | |
| 365 | * Add a server to the list of up servers and sort them by ping time | |
| 366 | */ | |
| 367 | 1 | var add_server = function(self, _server) { |
| 368 | // Emit a new server joined | |
| 369 | 0 | self.emit('joined', "mongos", null, _server); |
| 370 | // Get the server url | |
| 371 | 0 | var server_key = format("%s:%s", _server.host, _server.port); |
| 372 | // Push to list of valid server | |
| 373 | 0 | self.upServers[server_key] = _server; |
| 374 | // Remove the server from the list of downed servers | |
| 375 | 0 | delete self.downServers[server_key]; |
| 376 | ||
| 377 | // Sort the keys by ping time | |
| 378 | 0 | var keys = Object.keys(self.upServers); |
| 379 | 0 | var _upServersSorted = {}; |
| 380 | 0 | var _upServers = [] |
| 381 | ||
| 382 | // Get all the servers | |
| 383 | 0 | for(var name in self.upServers) { |
| 384 | 0 | _upServers.push(self.upServers[name]); |
| 385 | } | |
| 386 | ||
| 387 | // Sort all the server | |
| 388 | 0 | _upServers.sort(function(a, b) { |
| 389 | 0 | return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs']; |
| 390 | }); | |
| 391 | ||
| 392 | // Rebuild the upServer | |
| 393 | 0 | for(var i = 0; i < _upServers.length; i++) { |
| 394 | 0 | _upServersSorted[format("%s:%s", _upServers[i].host, _upServers[i].port)] = _upServers[i]; |
| 395 | } | |
| 396 | ||
| 397 | // Set the up servers | |
| 398 | 0 | self.upServers = _upServersSorted; |
| 399 | } | |
| 400 | ||
| 401 | /** | |
| 402 | * @ignore | |
| 403 | * Just return the currently picked active connection | |
| 404 | */ | |
| 405 | 1 | Mongos.prototype.allServerInstances = function() { |
| 406 | 0 | return this.servers; |
| 407 | } | |
| 408 | ||
| 409 | /** | |
| 410 | * Always ourselves | |
| 411 | * @ignore | |
| 412 | */ | |
| 413 | 1 | Mongos.prototype.setReadPreference = function() {} |
| 414 | ||
| 415 | /** | |
| 416 | * @ignore | |
| 417 | */ | |
| 418 | 1 | Mongos.prototype.allRawConnections = function() { |
| 419 | // Neeed to build a complete list of all raw connections, start with master server | |
| 420 | 0 | var allConnections = []; |
| 421 | // Get all connected connections | |
| 422 | 0 | for(var name in this.upServers) { |
| 423 | 0 | allConnections = allConnections.concat(this.upServers[name].allRawConnections()); |
| 424 | } | |
| 425 | // Return all the conections | |
| 426 | 0 | return allConnections; |
| 427 | } | |
| 428 | ||
| 429 | /** | |
| 430 | * @ignore | |
| 431 | */ | |
| 432 | 1 | Mongos.prototype.isConnected = function() { |
| 433 | 0 | return Object.keys(this.upServers).length > 0; |
| 434 | } | |
| 435 | ||
| 436 | /** | |
| 437 | * @ignore | |
| 438 | */ | |
| 439 | 1 | Mongos.prototype.isAutoReconnect = function() { |
| 440 | 0 | return true; |
| 441 | } | |
| 442 | ||
| 443 | /** | |
| 444 | * @ignore | |
| 445 | */ | |
| 446 | 1 | Mongos.prototype.canWrite = Mongos.prototype.isConnected; |
| 447 | ||
| 448 | /** | |
| 449 | * @ignore | |
| 450 | */ | |
| 451 | 1 | Mongos.prototype.canRead = Mongos.prototype.isConnected; |
| 452 | ||
| 453 | /** | |
| 454 | * @ignore | |
| 455 | */ | |
| 456 | 1 | Mongos.prototype.isDestroyed = function() { |
| 457 | 0 | return this._serverState == 'destroyed'; |
| 458 | } | |
| 459 | ||
| 460 | /** | |
| 461 | * @ignore | |
| 462 | */ | |
| 463 | 1 | Mongos.prototype.checkoutWriter = function() { |
| 464 | // Checkout a writer | |
| 465 | 0 | var keys = Object.keys(this.upServers); |
| 466 | // console.dir("============================ checkoutWriter :: " + keys.length) | |
| 467 | 0 | if(keys.length == 0) return null; |
| 468 | // console.log("=============== checkoutWriter :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port) | |
| 469 | 0 | return this.upServers[keys[0]].checkoutWriter(); |
| 470 | } | |
| 471 | ||
| 472 | /** | |
| 473 | * @ignore | |
| 474 | */ | |
| 475 | 1 | Mongos.prototype.checkoutReader = function(read) { |
| 476 | // console.log("=============== checkoutReader :: read :: " + read); | |
| 477 | // If read is set to null default to primary | |
| 478 | 0 | read = read || 'primary' |
| 479 | // If we have a read preference object unpack it | |
| 480 | 0 | if(read != null && typeof read == 'object' && read['_type'] == 'ReadPreference') { |
| 481 | // Validate if the object is using a valid mode | |
| 482 | 0 | if(!read.isValid()) throw new Error("Illegal readPreference mode specified, " + read.mode); |
| 483 | 0 | } else if(!ReadPreference.isValid(read)) { |
| 484 | 0 | throw new Error("Illegal readPreference mode specified, " + read); |
| 485 | } | |
| 486 | ||
| 487 | // Checkout a writer | |
| 488 | 0 | var keys = Object.keys(this.upServers); |
| 489 | 0 | if(keys.length == 0) return null; |
| 490 | // console.log("=============== checkoutReader :: " + this.upServers[keys[0]].checkoutWriter().socketOptions.port) | |
| 491 | // console.dir(this._commandsStore.commands) | |
| 492 | 0 | return this.upServers[keys[0]].checkoutWriter(); |
| 493 | } | |
| 494 | ||
| 495 | /** | |
| 496 | * @ignore | |
| 497 | */ | |
| 498 | 1 | Mongos.prototype.close = function(callback) { |
| 499 | 0 | var self = this; |
| 500 | // Set server status as disconnected | |
| 501 | 0 | this._serverState = 'destroyed'; |
| 502 | // Number of connections to close | |
| 503 | 0 | var numberOfConnectionsToClose = self.servers.length; |
| 504 | // If we have a ha process running kill it | |
| 505 | 0 | if(self._replicasetTimeoutId != null) clearInterval(self._replicasetTimeoutId); |
| 506 | 0 | self._replicasetTimeoutId = null; |
| 507 | ||
| 508 | // Emit close event | |
| 509 | 0 | processor(function() { |
| 510 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 511 | }); | |
| 512 | ||
| 513 | // Flush out any remaining call handlers | |
| 514 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 515 | ||
| 516 | // Close all the up servers | |
| 517 | 0 | for(var name in this.upServers) { |
| 518 | 0 | this.upServers[name].close(function(err, result) { |
| 519 | 0 | numberOfConnectionsToClose = numberOfConnectionsToClose - 1; |
| 520 | ||
| 521 | // Callback if we have one defined | |
| 522 | 0 | if(numberOfConnectionsToClose == 0 && typeof callback == 'function') { |
| 523 | 0 | callback(null); |
| 524 | } | |
| 525 | }); | |
| 526 | } | |
| 527 | } | |
| 528 | ||
| 529 | /** | |
| 530 | * @ignore | |
| 531 | * Return the used state | |
| 532 | */ | |
| 533 | 1 | Mongos.prototype._isUsed = function() { |
| 534 | 0 | return this._used; |
| 535 | } | |
| 536 | ||
| 537 | 1 | exports.Mongos = Mongos; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the Read Preference. | |
| 3 | * | |
| 4 | * Read Preferences | |
| 5 | * - **ReadPreference.PRIMARY**, Read from primary only. All operations produce an error (throw an exception where applicable) if primary is unavailable. Cannot be combined with tags (This is the default.). | |
| 6 | * - **ReadPreference.PRIMARY_PREFERRED**, Read from primary if available, otherwise a secondary. | |
| 7 | * - **ReadPreference.SECONDARY**, Read from secondary if available, otherwise error. | |
| 8 | * - **ReadPreference.SECONDARY_PREFERRED**, Read from a secondary if available, otherwise read from the primary. | |
| 9 | * - **ReadPreference.NEAREST**, All modes read from among the nearest candidates, but unlike other modes, NEAREST will include both the primary and all secondaries in the random selection. | |
| 10 | * | |
| 11 | * @class Represents a Read Preference. | |
| 12 | * @param {String} the read preference type | |
| 13 | * @param {Object} tags | |
| 14 | * @return {ReadPreference} | |
| 15 | */ | |
| 16 | 1 | var ReadPreference = function(mode, tags) { |
| 17 | 0 | if(!(this instanceof ReadPreference)) |
| 18 | 0 | return new ReadPreference(mode, tags); |
| 19 | 0 | this._type = 'ReadPreference'; |
| 20 | 0 | this.mode = mode; |
| 21 | 0 | this.tags = tags; |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | */ | |
| 27 | 1 | ReadPreference.isValid = function(_mode) { |
| 28 | 0 | return (_mode == ReadPreference.PRIMARY || _mode == ReadPreference.PRIMARY_PREFERRED |
| 29 | || _mode == ReadPreference.SECONDARY || _mode == ReadPreference.SECONDARY_PREFERRED | |
| 30 | || _mode == ReadPreference.NEAREST | |
| 31 | || _mode == true || _mode == false); | |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * @ignore | |
| 36 | */ | |
| 37 | 1 | ReadPreference.prototype.isValid = function(mode) { |
| 38 | 0 | var _mode = typeof mode == 'string' ? mode : this.mode; |
| 39 | 0 | return ReadPreference.isValid(_mode); |
| 40 | } | |
| 41 | ||
| 42 | /** | |
| 43 | * @ignore | |
| 44 | */ | |
| 45 | 1 | ReadPreference.prototype.toObject = function() { |
| 46 | 0 | var object = {mode:this.mode}; |
| 47 | ||
| 48 | 0 | if(this.tags != null) { |
| 49 | 0 | object['tags'] = this.tags; |
| 50 | } | |
| 51 | ||
| 52 | 0 | return object; |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| 56 | * @ignore | |
| 57 | */ | |
| 58 | 1 | ReadPreference.PRIMARY = 'primary'; |
| 59 | 1 | ReadPreference.PRIMARY_PREFERRED = 'primaryPreferred'; |
| 60 | 1 | ReadPreference.SECONDARY = 'secondary'; |
| 61 | 1 | ReadPreference.SECONDARY_PREFERRED = 'secondaryPreferred'; |
| 62 | 1 | ReadPreference.NEAREST = 'nearest' |
| 63 | ||
| 64 | /** | |
| 65 | * @ignore | |
| 66 | */ | |
| 67 | 1 | exports.ReadPreference = ReadPreference; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var DbCommand = require('../../commands/db_command').DbCommand |
| 2 | , format = require('util').format; | |
| 3 | ||
| 4 | 1 | var HighAvailabilityProcess = function(replset, options) { |
| 5 | 0 | this.replset = replset; |
| 6 | 0 | this.options = options; |
| 7 | 0 | this.server = null; |
| 8 | 0 | this.state = HighAvailabilityProcess.INIT; |
| 9 | 0 | this.selectedIndex = 0; |
| 10 | } | |
| 11 | ||
| 12 | 1 | HighAvailabilityProcess.INIT = 'init'; |
| 13 | 1 | HighAvailabilityProcess.RUNNING = 'running'; |
| 14 | 1 | HighAvailabilityProcess.STOPPED = 'stopped'; |
| 15 | ||
| 16 | 1 | HighAvailabilityProcess.prototype.start = function() { |
| 17 | 0 | var self = this; |
| 18 | 0 | if(this.replset._state |
| 19 | && Object.keys(this.replset._state.addresses).length == 0) { | |
| 20 | 0 | if(this.server) this.server.close(); |
| 21 | 0 | this.state = HighAvailabilityProcess.STOPPED; |
| 22 | 0 | return; |
| 23 | } | |
| 24 | ||
| 25 | 0 | if(this.server) this.server.close(); |
| 26 | // Start the running | |
| 27 | 0 | this._haProcessInProcess = false; |
| 28 | 0 | this.state = HighAvailabilityProcess.RUNNING; |
| 29 | ||
| 30 | // Get all possible reader servers | |
| 31 | 0 | var candidate_servers = this.replset._state.getAllReadServers(); |
| 32 | 0 | if(candidate_servers.length == 0) { |
| 33 | 0 | return; |
| 34 | } | |
| 35 | ||
| 36 | // Select a candidate server for the connection | |
| 37 | 0 | var server = candidate_servers[this.selectedIndex % candidate_servers.length]; |
| 38 | 0 | this.selectedIndex = this.selectedIndex + 1; |
| 39 | ||
| 40 | // Unpack connection options | |
| 41 | 0 | var connectTimeoutMS = self.options.connectTimeoutMS || 10000; |
| 42 | 0 | var socketTimeoutMS = self.options.socketTimeoutMS || 30000; |
| 43 | ||
| 44 | // Just ensure we don't have a full cycle dependency | |
| 45 | 0 | var Db = require('../../db').Db |
| 46 | 0 | var Server = require('../server').Server; |
| 47 | ||
| 48 | // Set up a new server instance | |
| 49 | 0 | var newServer = new Server(server.host, server.port, { |
| 50 | auto_reconnect: false | |
| 51 | , returnIsMasterResults: true | |
| 52 | , poolSize: 1 | |
| 53 | , socketOptions: { | |
| 54 | connectTimeoutMS: connectTimeoutMS, | |
| 55 | socketTimeoutMS: socketTimeoutMS, | |
| 56 | keepAlive: 100 | |
| 57 | } | |
| 58 | , ssl: this.options.ssl | |
| 59 | , sslValidate: this.options.sslValidate | |
| 60 | , sslCA: this.options.sslCA | |
| 61 | , sslCert: this.options.sslCert | |
| 62 | , sslKey: this.options.sslKey | |
| 63 | , sslPass: this.options.sslPass | |
| 64 | }); | |
| 65 | ||
| 66 | // Create new dummy db for app | |
| 67 | 0 | self.db = new Db('local', newServer, {w:1}); |
| 68 | ||
| 69 | // Set up the event listeners | |
| 70 | 0 | newServer.once("error", _handle(this, newServer)); |
| 71 | 0 | newServer.once("close", _handle(this, newServer)); |
| 72 | 0 | newServer.once("timeout", _handle(this, newServer)); |
| 73 | 0 | newServer.name = format("%s:%s", server.host, server.port); |
| 74 | ||
| 75 | // Let's attempt a connection over here | |
| 76 | 0 | newServer.connect(self.db, function(err, result, _server) { |
| 77 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 78 | 0 | _server.close(); |
| 79 | } | |
| 80 | ||
| 81 | 0 | if(err) { |
| 82 | // Close the server | |
| 83 | 0 | _server.close(); |
| 84 | // Check if we can even do HA (is there anything running) | |
| 85 | 0 | if(Object.keys(self.replset._state.addresses).length == 0) { |
| 86 | 0 | return; |
| 87 | } | |
| 88 | ||
| 89 | // Let's boot the ha timeout settings | |
| 90 | 0 | setTimeout(function() { |
| 91 | 0 | self.start(); |
| 92 | }, self.options.haInterval); | |
| 93 | } else { | |
| 94 | 0 | self.server = _server; |
| 95 | // Let's boot the ha timeout settings | |
| 96 | 0 | setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 97 | } | |
| 98 | }); | |
| 99 | } | |
| 100 | ||
| 101 | 1 | HighAvailabilityProcess.prototype.stop = function() { |
| 102 | 0 | this.state = HighAvailabilityProcess.STOPPED; |
| 103 | 0 | if(this.server) this.server.close(); |
| 104 | } | |
| 105 | ||
| 106 | 1 | var _timeoutHandle = function(self) { |
| 107 | 0 | return function() { |
| 108 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 109 | // Stop all server instances | |
| 110 | 0 | for(var name in self.replset._state.addresses) { |
| 111 | 0 | self.replset._state.addresses[name].close(); |
| 112 | 0 | delete self.replset._state.addresses[name]; |
| 113 | } | |
| 114 | ||
| 115 | // Finished pinging | |
| 116 | 0 | return; |
| 117 | } | |
| 118 | ||
| 119 | // If the server is connected | |
| 120 | 0 | if(self.server.isConnected() && !self._haProcessInProcess) { |
| 121 | // Start HA process | |
| 122 | 0 | self._haProcessInProcess = true; |
| 123 | // Execute is master command | |
| 124 | 0 | self.db._executeQueryCommand(DbCommand.createIsMasterCommand(self.db), |
| 125 | {failFast:true, connection: self.server.checkoutReader()} | |
| 126 | , function(err, res) { | |
| 127 | 0 | if(err) { |
| 128 | 0 | self.server.close(); |
| 129 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 130 | } | |
| 131 | ||
| 132 | // Master document | |
| 133 | 0 | var master = res.documents[0]; |
| 134 | 0 | var hosts = master.hosts || []; |
| 135 | 0 | var reconnect_servers = []; |
| 136 | 0 | var state = self.replset._state; |
| 137 | ||
| 138 | // We are in recovery mode, let's remove the current server | |
| 139 | 0 | if(!master.ismaster |
| 140 | && !master.secondary | |
| 141 | && state.addresses[master.me]) { | |
| 142 | 0 | self.server.close(); |
| 143 | 0 | state.addresses[master.me].close(); |
| 144 | 0 | delete state.secondaries[master.me]; |
| 145 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 146 | } | |
| 147 | ||
| 148 | // For all the hosts let's check that we have connections | |
| 149 | 0 | for(var i = 0; i < hosts.length; i++) { |
| 150 | 0 | var host = hosts[i]; |
| 151 | // Check if we need to reconnect to a server | |
| 152 | 0 | if(state.addresses[host] == null) { |
| 153 | 0 | reconnect_servers.push(host); |
| 154 | 0 | } else if(state.addresses[host] && !state.addresses[host].isConnected()) { |
| 155 | 0 | state.addresses[host].close(); |
| 156 | 0 | delete state.secondaries[host]; |
| 157 | 0 | reconnect_servers.push(host); |
| 158 | } | |
| 159 | ||
| 160 | 0 | if((master.primary && state.master == null) |
| 161 | || (master.primary && state.master.name != master.primary)) { | |
| 162 | ||
| 163 | // Locate the primary and set it | |
| 164 | 0 | if(state.addresses[master.primary]) { |
| 165 | 0 | if(state.master) state.master.close(); |
| 166 | 0 | delete state.secondaries[master.primary]; |
| 167 | 0 | state.master = state.addresses[master.primary]; |
| 168 | } | |
| 169 | ||
| 170 | // Set up the changes | |
| 171 | 0 | if(state.master != null && state.master.isMasterDoc != null) { |
| 172 | 0 | state.master.isMasterDoc.ismaster = true; |
| 173 | 0 | state.master.isMasterDoc.secondary = false; |
| 174 | 0 | } else if(state.master != null) { |
| 175 | 0 | state.master.isMasterDoc = master; |
| 176 | 0 | state.master.isMasterDoc.ismaster = true; |
| 177 | 0 | state.master.isMasterDoc.secondary = false; |
| 178 | } | |
| 179 | ||
| 180 | // Execute any waiting commands (queries or writes) | |
| 181 | 0 | self.replset._commandsStore.execute_queries(); |
| 182 | 0 | self.replset._commandsStore.execute_writes(); |
| 183 | } | |
| 184 | } | |
| 185 | ||
| 186 | // Let's reconnect to any server needed | |
| 187 | 0 | if(reconnect_servers.length > 0) { |
| 188 | 0 | _reconnect_servers(self, reconnect_servers); |
| 189 | } else { | |
| 190 | 0 | self._haProcessInProcess = false |
| 191 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 192 | } | |
| 193 | }); | |
| 194 | 0 | } else if(!self.server.isConnected()) { |
| 195 | 0 | setTimeout(function() { |
| 196 | 0 | return self.start(); |
| 197 | }, self.options.haInterval); | |
| 198 | } else { | |
| 199 | 0 | setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 200 | } | |
| 201 | } | |
| 202 | } | |
| 203 | ||
| 204 | 1 | var _reconnect_servers = function(self, reconnect_servers) { |
| 205 | 0 | if(reconnect_servers.length == 0) { |
| 206 | 0 | self._haProcessInProcess = false |
| 207 | 0 | return setTimeout(_timeoutHandle(self), self.options.haInterval); |
| 208 | } | |
| 209 | ||
| 210 | // Unpack connection options | |
| 211 | 0 | var connectTimeoutMS = self.options.connectTimeoutMS || 10000; |
| 212 | 0 | var socketTimeoutMS = self.options.socketTimeoutMS || 0; |
| 213 | ||
| 214 | // Server class | |
| 215 | 0 | var Db = require('../../db').Db |
| 216 | 0 | var Server = require('../server').Server; |
| 217 | // Get the host | |
| 218 | 0 | var host = reconnect_servers.shift(); |
| 219 | // Split it up | |
| 220 | 0 | var _host = host.split(":")[0]; |
| 221 | 0 | var _port = parseInt(host.split(":")[1], 10); |
| 222 | ||
| 223 | // Set up a new server instance | |
| 224 | 0 | var newServer = new Server(_host, _port, { |
| 225 | auto_reconnect: false | |
| 226 | , returnIsMasterResults: true | |
| 227 | , poolSize: self.options.poolSize | |
| 228 | , socketOptions: { | |
| 229 | connectTimeoutMS: connectTimeoutMS, | |
| 230 | socketTimeoutMS: socketTimeoutMS | |
| 231 | } | |
| 232 | , ssl: self.options.ssl | |
| 233 | , sslValidate: self.options.sslValidate | |
| 234 | , sslCA: self.options.sslCA | |
| 235 | , sslCert: self.options.sslCert | |
| 236 | , sslKey: self.options.sslKey | |
| 237 | , sslPass: self.options.sslPass | |
| 238 | }); | |
| 239 | ||
| 240 | // Create new dummy db for app | |
| 241 | 0 | var db = new Db('local', newServer, {w:1}); |
| 242 | 0 | var state = self.replset._state; |
| 243 | ||
| 244 | // Set up the event listeners | |
| 245 | 0 | newServer.once("error", _repl_set_handler("error", self.replset, newServer)); |
| 246 | 0 | newServer.once("close", _repl_set_handler("close", self.replset, newServer)); |
| 247 | 0 | newServer.once("timeout", _repl_set_handler("timeout", self.replset, newServer)); |
| 248 | ||
| 249 | // Set shared state | |
| 250 | 0 | newServer.name = host; |
| 251 | 0 | newServer._callBackStore = self.replset._callBackStore; |
| 252 | 0 | newServer.replicasetInstance = self.replset; |
| 253 | 0 | newServer.enableRecordQueryStats(self.replset.recordQueryStats); |
| 254 | ||
| 255 | // Let's attempt a connection over here | |
| 256 | 0 | newServer.connect(db, function(err, result, _server) { |
| 257 | 0 | if(self.state == HighAvailabilityProcess.STOPPED) { |
| 258 | 0 | _server.close(); |
| 259 | } | |
| 260 | ||
| 261 | // If we connected let's check what kind of server we have | |
| 262 | 0 | if(!err) { |
| 263 | 0 | _apply_auths(self, db, _server, function(err, result) { |
| 264 | 0 | if(err) { |
| 265 | 0 | _server.close(); |
| 266 | // Process the next server | |
| 267 | 0 | return setTimeout(function() { |
| 268 | 0 | _reconnect_servers(self, reconnect_servers); |
| 269 | }, self.options.haInterval); | |
| 270 | } | |
| 271 | 0 | var doc = _server.isMasterDoc; |
| 272 | // Fire error on any unknown callbacks for this server | |
| 273 | 0 | self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err); |
| 274 | ||
| 275 | 0 | if(doc.ismaster) { |
| 276 | // Emit primary added | |
| 277 | 0 | self.replset.emit('joined', "primary", doc, _server); |
| 278 | ||
| 279 | // If it was a secondary remove it | |
| 280 | 0 | if(state.secondaries[doc.me]) { |
| 281 | 0 | delete state.secondaries[doc.me]; |
| 282 | } | |
| 283 | ||
| 284 | // Override any server in list of addresses | |
| 285 | 0 | state.addresses[doc.me] = _server; |
| 286 | // Set server as master | |
| 287 | 0 | state.master = _server; |
| 288 | // Execute any waiting writes | |
| 289 | 0 | self.replset._commandsStore.execute_writes(); |
| 290 | 0 | } else if(doc.secondary) { |
| 291 | // Emit secondary added | |
| 292 | 0 | self.replset.emit('joined', "secondary", doc, _server); |
| 293 | // Add the secondary to the state | |
| 294 | 0 | state.secondaries[doc.me] = _server; |
| 295 | // Override any server in list of addresses | |
| 296 | 0 | state.addresses[doc.me] = _server; |
| 297 | // Execute any waiting reads | |
| 298 | 0 | self.replset._commandsStore.execute_queries(); |
| 299 | } else { | |
| 300 | 0 | _server.close(); |
| 301 | } | |
| 302 | ||
| 303 | // Set any tags on the instance server | |
| 304 | 0 | _server.name = doc.me; |
| 305 | 0 | _server.tags = doc.tags; |
| 306 | // Process the next server | |
| 307 | 0 | setTimeout(function() { |
| 308 | 0 | _reconnect_servers(self, reconnect_servers); |
| 309 | }, self.options.haInterval); | |
| 310 | }); | |
| 311 | } else { | |
| 312 | 0 | _server.close(); |
| 313 | 0 | self.replset.__executeAllServerSpecificErrorCallbacks(_server.socketOptions.host, _server.socketOptions.port, err); |
| 314 | ||
| 315 | 0 | setTimeout(function() { |
| 316 | 0 | _reconnect_servers(self, reconnect_servers); |
| 317 | }, self.options.haInterval); | |
| 318 | } | |
| 319 | }); | |
| 320 | } | |
| 321 | ||
| 322 | 1 | var _apply_auths = function(self, _db, _server, _callback) { |
| 323 | 0 | if(self.replset.auth.length() == 0) return _callback(null); |
| 324 | // Apply any authentication needed | |
| 325 | 0 | if(self.replset.auth.length() > 0) { |
| 326 | 0 | var pending = self.replset.auth.length(); |
| 327 | 0 | var connections = _server.allRawConnections(); |
| 328 | 0 | var pendingAuthConn = connections.length; |
| 329 | ||
| 330 | // Connection function | |
| 331 | 0 | var connectionFunction = function(_auth, _connection, __callback) { |
| 332 | 0 | var pending = _auth.length(); |
| 333 | ||
| 334 | 0 | for(var j = 0; j < pending; j++) { |
| 335 | // Get the auth object | |
| 336 | 0 | var _auth = _auth.get(j); |
| 337 | // Unpack the parameter | |
| 338 | 0 | var username = _auth.username; |
| 339 | 0 | var password = _auth.password; |
| 340 | 0 | var options = { |
| 341 | authMechanism: _auth.authMechanism | |
| 342 | , authSource: _auth.authdb | |
| 343 | , connection: _connection | |
| 344 | }; | |
| 345 | ||
| 346 | // If we have changed the service name | |
| 347 | 0 | if(_auth.gssapiServiceName) |
| 348 | 0 | options.gssapiServiceName = _auth.gssapiServiceName; |
| 349 | ||
| 350 | // Hold any error | |
| 351 | 0 | var _error = null; |
| 352 | ||
| 353 | // Authenticate against the credentials | |
| 354 | 0 | _db.authenticate(username, password, options, function(err, result) { |
| 355 | 0 | _error = err != null ? err : _error; |
| 356 | // Adjust the pending authentication | |
| 357 | 0 | pending = pending - 1; |
| 358 | // Finished up | |
| 359 | 0 | if(pending == 0) __callback(_error ? _error : null, _error ? false : true); |
| 360 | }); | |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | // Final error object | |
| 365 | 0 | var finalError = null; |
| 366 | // Iterate over all the connections | |
| 367 | 0 | for(var i = 0; i < connections.length; i++) { |
| 368 | 0 | connectionFunction(self.replset.auth, connections[i], function(err, result) { |
| 369 | // Pending authentication | |
| 370 | 0 | pendingAuthConn = pendingAuthConn - 1 ; |
| 371 | ||
| 372 | // Save error if any | |
| 373 | 0 | finalError = err ? err : finalError; |
| 374 | ||
| 375 | // If we are done let's finish up | |
| 376 | 0 | if(pendingAuthConn == 0) { |
| 377 | 0 | _callback(null); |
| 378 | } | |
| 379 | }); | |
| 380 | } | |
| 381 | } | |
| 382 | } | |
| 383 | ||
| 384 | 1 | var _handle = function(self, server) { |
| 385 | 0 | return function(err) { |
| 386 | 0 | server.close(); |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | 1 | var _repl_set_handler = function(event, self, server) { |
| 391 | 0 | var ReplSet = require('./repl_set').ReplSet; |
| 392 | ||
| 393 | 0 | return function(err, doc) { |
| 394 | 0 | server.close(); |
| 395 | ||
| 396 | // The event happened to a primary | |
| 397 | // Remove it from play | |
| 398 | 0 | if(self._state.isPrimary(server)) { |
| 399 | 0 | self._state.master == null; |
| 400 | 0 | self._serverState = ReplSet.REPLSET_READ_ONLY; |
| 401 | 0 | } else if(self._state.isSecondary(server)) { |
| 402 | 0 | delete self._state.secondaries[server.name]; |
| 403 | } | |
| 404 | ||
| 405 | // Unpack variables | |
| 406 | 0 | var host = server.socketOptions.host; |
| 407 | 0 | var port = server.socketOptions.port; |
| 408 | ||
| 409 | // Fire error on any unknown callbacks | |
| 410 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 411 | } | |
| 412 | } | |
| 413 | ||
| 414 | 1 | exports.HighAvailabilityProcess = HighAvailabilityProcess; |
| 415 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var PingStrategy = require('./strategies/ping_strategy').PingStrategy |
| 2 | , StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy | |
| 3 | , ReadPreference = require('../read_preference').ReadPreference; | |
| 4 | ||
| 5 | 1 | var Options = function(options) { |
| 6 | 0 | options = options || {}; |
| 7 | 0 | this._options = options; |
| 8 | 0 | this.ha = options.ha || true; |
| 9 | 0 | this.haInterval = options.haInterval || 2000; |
| 10 | 0 | this.reconnectWait = options.reconnectWait || 1000; |
| 11 | 0 | this.retries = options.retries || 30; |
| 12 | 0 | this.rs_name = options.rs_name; |
| 13 | 0 | this.socketOptions = options.socketOptions || {}; |
| 14 | 0 | this.readPreference = options.readPreference; |
| 15 | 0 | this.readSecondary = options.read_secondary; |
| 16 | 0 | this.poolSize = options.poolSize == null ? 5 : options.poolSize; |
| 17 | 0 | this.strategy = options.strategy || 'ping'; |
| 18 | 0 | this.secondaryAcceptableLatencyMS = options.secondaryAcceptableLatencyMS || 15; |
| 19 | 0 | this.connectArbiter = options.connectArbiter || false; |
| 20 | 0 | this.connectWithNoPrimary = options.connectWithNoPrimary || false; |
| 21 | 0 | this.logger = options.logger; |
| 22 | 0 | this.ssl = options.ssl || false; |
| 23 | 0 | this.sslValidate = options.sslValidate || false; |
| 24 | 0 | this.sslCA = options.sslCA; |
| 25 | 0 | this.sslCert = options.sslCert; |
| 26 | 0 | this.sslKey = options.sslKey; |
| 27 | 0 | this.sslPass = options.sslPass; |
| 28 | 0 | this.emitOpen = options.emitOpen || true; |
| 29 | } | |
| 30 | ||
| 31 | 1 | Options.prototype.init = function() { |
| 32 | 0 | if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) { |
| 33 | 0 | throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate"); |
| 34 | } | |
| 35 | ||
| 36 | // Make sure strategy is one of the two allowed | |
| 37 | 0 | if(this.strategy != null && (this.strategy != 'ping' && this.strategy != 'statistical' && this.strategy != 'none')) |
| 38 | 0 | throw new Error("Only ping or statistical strategies allowed"); |
| 39 | ||
| 40 | 0 | if(this.strategy == null) this.strategy = 'ping'; |
| 41 | ||
| 42 | // Set logger if strategy exists | |
| 43 | 0 | if(this.strategyInstance) this.strategyInstance.logger = this.logger; |
| 44 | ||
| 45 | // Unpack read Preference | |
| 46 | 0 | var readPreference = this.readPreference; |
| 47 | // Validate correctness of Read preferences | |
| 48 | 0 | if(readPreference != null) { |
| 49 | 0 | if(readPreference != ReadPreference.PRIMARY && readPreference != ReadPreference.PRIMARY_PREFERRED |
| 50 | && readPreference != ReadPreference.SECONDARY && readPreference != ReadPreference.SECONDARY_PREFERRED | |
| 51 | && readPreference != ReadPreference.NEAREST && typeof readPreference != 'object' && readPreference['_type'] != 'ReadPreference') { | |
| 52 | 0 | throw new Error("Illegal readPreference mode specified, " + readPreference); |
| 53 | } | |
| 54 | ||
| 55 | 0 | this.readPreference = readPreference; |
| 56 | } else { | |
| 57 | 0 | this.readPreference = null; |
| 58 | } | |
| 59 | ||
| 60 | // Ensure read_secondary is set correctly | |
| 61 | 0 | if(this.readSecondary != null) |
| 62 | 0 | this.readSecondary = this.readPreference == ReadPreference.PRIMARY |
| 63 | || this.readPreference == false | |
| 64 | || this.readPreference == null ? false : true; | |
| 65 | ||
| 66 | // Ensure correct slave set | |
| 67 | 0 | if(this.readSecondary) this.slaveOk = true; |
| 68 | ||
| 69 | // Set up logger if any set | |
| 70 | 0 | this.logger = this.logger != null |
| 71 | && (typeof this.logger.debug == 'function') | |
| 72 | && (typeof this.logger.error == 'function') | |
| 73 | && (typeof this.logger.debug == 'function') | |
| 74 | ? this.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 75 | ||
| 76 | // Connection timeout | |
| 77 | 0 | this.connectTimeoutMS = this.socketOptions.connectTimeoutMS |
| 78 | ? this.socketOptions.connectTimeoutMS | |
| 79 | : 1000; | |
| 80 | ||
| 81 | // Socket connection timeout | |
| 82 | 0 | this.socketTimeoutMS = this.socketOptions.socketTimeoutMS |
| 83 | ? this.socketOptions.socketTimeoutMS | |
| 84 | : 30000; | |
| 85 | } | |
| 86 | ||
| 87 | 1 | Options.prototype.decorateAndClean = function(servers, callBackStore) { |
| 88 | 0 | var self = this; |
| 89 | ||
| 90 | // var de duplicate list | |
| 91 | 0 | var uniqueServers = {}; |
| 92 | // De-duplicate any servers in the seed list | |
| 93 | 0 | for(var i = 0; i < servers.length; i++) { |
| 94 | 0 | var server = servers[i]; |
| 95 | // If server does not exist set it | |
| 96 | 0 | if(uniqueServers[server.host + ":" + server.port] == null) { |
| 97 | 0 | uniqueServers[server.host + ":" + server.port] = server; |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | // Let's set the deduplicated list of servers | |
| 102 | 0 | var finalServers = []; |
| 103 | // Add the servers | |
| 104 | 0 | for(var key in uniqueServers) { |
| 105 | 0 | finalServers.push(uniqueServers[key]); |
| 106 | } | |
| 107 | ||
| 108 | 0 | finalServers.forEach(function(server) { |
| 109 | // Ensure no server has reconnect on | |
| 110 | 0 | server.options.auto_reconnect = false; |
| 111 | // Set up ssl options | |
| 112 | 0 | server.ssl = self.ssl; |
| 113 | 0 | server.sslValidate = self.sslValidate; |
| 114 | 0 | server.sslCA = self.sslCA; |
| 115 | 0 | server.sslCert = self.sslCert; |
| 116 | 0 | server.sslKey = self.sslKey; |
| 117 | 0 | server.sslPass = self.sslPass; |
| 118 | 0 | server.poolSize = self.poolSize; |
| 119 | // Set callback store | |
| 120 | 0 | server._callBackStore = callBackStore; |
| 121 | }); | |
| 122 | ||
| 123 | 0 | return finalServers; |
| 124 | } | |
| 125 | ||
| 126 | 1 | exports.Options = Options; |
| 127 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ReadPreference = require('../read_preference').ReadPreference |
| 2 | , DbCommand = require('../../commands/db_command').DbCommand | |
| 3 | , inherits = require('util').inherits | |
| 4 | , format = require('util').format | |
| 5 | , timers = require('timers') | |
| 6 | , Server = require('../server').Server | |
| 7 | , utils = require('../../utils') | |
| 8 | , PingStrategy = require('./strategies/ping_strategy').PingStrategy | |
| 9 | , StatisticsStrategy = require('./strategies/statistics_strategy').StatisticsStrategy | |
| 10 | , Options = require('./options').Options | |
| 11 | , ReplSetState = require('./repl_set_state').ReplSetState | |
| 12 | , HighAvailabilityProcess = require('./ha').HighAvailabilityProcess | |
| 13 | , Base = require('../base').Base; | |
| 14 | ||
| 15 | 1 | var STATE_STARTING_PHASE_1 = 0; |
| 16 | 1 | var STATE_PRIMARY = 1; |
| 17 | 1 | var STATE_SECONDARY = 2; |
| 18 | 1 | var STATE_RECOVERING = 3; |
| 19 | 1 | var STATE_FATAL_ERROR = 4; |
| 20 | 1 | var STATE_STARTING_PHASE_2 = 5; |
| 21 | 1 | var STATE_UNKNOWN = 6; |
| 22 | 1 | var STATE_ARBITER = 7; |
| 23 | 1 | var STATE_DOWN = 8; |
| 24 | 1 | var STATE_ROLLBACK = 9; |
| 25 | ||
| 26 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 27 | 1 | var processor = require('../../utils').processor(); |
| 28 | ||
| 29 | /** | |
| 30 | * ReplSet constructor provides replicaset functionality | |
| 31 | * | |
| 32 | * Options | |
| 33 | * - **ha** {Boolean, default:true}, turn on high availability. | |
| 34 | * - **haInterval** {Number, default:2000}, time between each replicaset status check. | |
| 35 | * - **reconnectWait** {Number, default:1000}, time to wait in miliseconds before attempting reconnect. | |
| 36 | * - **retries** {Number, default:30}, number of times to attempt a replicaset reconnect. | |
| 37 | * - **rs_name** {String}, the name of the replicaset to connect to. | |
| 38 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 39 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 40 | * - **strategy** {String, default:'ping'}, selection strategy for reads choose between (ping, statistical and none, default is ping) | |
| 41 | * - **secondaryAcceptableLatencyMS** {Number, default:15}, sets the range of servers to pick when using NEAREST (lowest ping ms + the latency fence, ex: range of 1 to (1 + 15) ms) | |
| 42 | * - **connectWithNoPrimary** {Boolean, default:false}, sets if the driver should connect even if no primary is available | |
| 43 | * - **connectArbiter** {Boolean, default:false}, sets if the driver should connect to arbiters or not. | |
| 44 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 45 | * - **poolSize** {Number, default:5}, number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons. | |
| 46 | * - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support) | |
| 47 | * - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 48 | * - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 49 | * - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 50 | * - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 51 | * - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 52 | * | |
| 53 | * @class Represents a | |
| 54 | Replicaset Configuration | |
| 55 | * @param {Array} list of server objects participating in the replicaset. | |
| 56 | * @param {Object} [options] additional options for the replicaset connection. | |
| 57 | */ | |
| 58 | 1 | var ReplSet = exports.ReplSet = function(servers, options) { |
| 59 | // Set up basic | |
| 60 | 0 | if(!(this instanceof ReplSet)) |
| 61 | 0 | return new ReplSet(servers, options); |
| 62 | ||
| 63 | // Set up event emitter | |
| 64 | 0 | Base.call(this); |
| 65 | ||
| 66 | // Ensure we have a list of servers | |
| 67 | 0 | if(!Array.isArray(servers)) throw Error("The parameter must be an array of servers and contain at least one server"); |
| 68 | // Ensure no Mongos's | |
| 69 | 0 | for(var i = 0; i < servers.length; i++) { |
| 70 | 0 | if(!(servers[i] instanceof Server)) throw new Error("list of servers must be of type Server"); |
| 71 | } | |
| 72 | ||
| 73 | // Save the options | |
| 74 | 0 | this.options = new Options(options); |
| 75 | // Ensure basic validation of options | |
| 76 | 0 | this.options.init(); |
| 77 | ||
| 78 | // Server state | |
| 79 | 0 | this._serverState = ReplSet.REPLSET_DISCONNECTED; |
| 80 | // Add high availability process | |
| 81 | 0 | this._haProcess = new HighAvailabilityProcess(this, this.options); |
| 82 | ||
| 83 | // Let's iterate over all the provided server objects and decorate them | |
| 84 | 0 | this.servers = this.options.decorateAndClean(servers, this._callBackStore); |
| 85 | // Throw error if no seed servers | |
| 86 | 0 | if(this.servers.length == 0) throw new Error("No valid seed servers in the array"); |
| 87 | ||
| 88 | // Let's set up our strategy object for picking secondaries | |
| 89 | 0 | if(this.options.strategy == 'ping') { |
| 90 | // Create a new instance | |
| 91 | 0 | this.strategyInstance = new PingStrategy(this, this.options.secondaryAcceptableLatencyMS); |
| 92 | 0 | } else if(this.options.strategy == 'statistical') { |
| 93 | // Set strategy as statistical | |
| 94 | 0 | this.strategyInstance = new StatisticsStrategy(this); |
| 95 | // Add enable query information | |
| 96 | 0 | this.enableRecordQueryStats(true); |
| 97 | } | |
| 98 | ||
| 99 | 0 | this.emitOpen = this.options.emitOpen || true; |
| 100 | // Set up a clean state | |
| 101 | 0 | this._state = new ReplSetState(this); |
| 102 | // Current round robin selected server | |
| 103 | 0 | this._currentServerChoice = 0; |
| 104 | // Ensure up the server callbacks | |
| 105 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 106 | 0 | this.servers[i]._callBackStore = this._callBackStore; |
| 107 | 0 | this.servers[i].name = format("%s:%s", this.servers[i].host, this.servers[i].port) |
| 108 | 0 | this.servers[i].replicasetInstance = this; |
| 109 | 0 | this.servers[i].options.auto_reconnect = false; |
| 110 | 0 | this.servers[i].inheritReplSetOptionsFrom(this); |
| 111 | } | |
| 112 | ||
| 113 | // Allow setting the socketTimeoutMS on all connections | |
| 114 | // to work around issues such as secondaries blocking due to compaction | |
| 115 | 0 | utils.setSocketTimeoutProperty(this, this.options.socketOptions); |
| 116 | } | |
| 117 | ||
| 118 | /** | |
| 119 | * @ignore | |
| 120 | */ | |
| 121 | 1 | inherits(ReplSet, Base); |
| 122 | ||
| 123 | // Replicaset states | |
| 124 | 1 | ReplSet.REPLSET_CONNECTING = 'connecting'; |
| 125 | 1 | ReplSet.REPLSET_DISCONNECTED = 'disconnected'; |
| 126 | 1 | ReplSet.REPLSET_CONNECTED = 'connected'; |
| 127 | 1 | ReplSet.REPLSET_RECONNECTING = 'reconnecting'; |
| 128 | 1 | ReplSet.REPLSET_DESTROYED = 'destroyed'; |
| 129 | 1 | ReplSet.REPLSET_READ_ONLY = 'readonly'; |
| 130 | ||
| 131 | 1 | ReplSet.prototype.isAutoReconnect = function() { |
| 132 | 0 | return true; |
| 133 | } | |
| 134 | ||
| 135 | 1 | ReplSet.prototype.canWrite = function() { |
| 136 | 0 | return this._state.master && this._state.master.isConnected(); |
| 137 | } | |
| 138 | ||
| 139 | 1 | ReplSet.prototype.canRead = function(read) { |
| 140 | 0 | if((read == ReadPreference.PRIMARY |
| 141 | 0 | || read == null || read == false) && (this._state.master == null || !this._state.master.isConnected())) return false; |
| 142 | 0 | return Object.keys(this._state.secondaries).length > 0; |
| 143 | } | |
| 144 | ||
| 145 | /** | |
| 146 | * @ignore | |
| 147 | */ | |
| 148 | 1 | ReplSet.prototype.enableRecordQueryStats = function(enable) { |
| 149 | // Set the global enable record query stats | |
| 150 | 0 | this.recordQueryStats = enable; |
| 151 | ||
| 152 | // Enable all the servers | |
| 153 | 0 | for(var i = 0; i < this.servers.length; i++) { |
| 154 | 0 | this.servers[i].enableRecordQueryStats(enable); |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | /** | |
| 159 | * @ignore | |
| 160 | */ | |
| 161 | 1 | ReplSet.prototype.setReadPreference = function(preference) { |
| 162 | 0 | this.options.readPreference = preference; |
| 163 | } | |
| 164 | ||
| 165 | 1 | ReplSet.prototype.connect = function(parent, options, callback) { |
| 166 | 0 | if(this._serverState != ReplSet.REPLSET_DISCONNECTED) |
| 167 | 0 | return callback(new Error("in process of connection")); |
| 168 | ||
| 169 | // If no callback throw | |
| 170 | 0 | if(!(typeof callback == 'function')) |
| 171 | 0 | throw new Error("cannot call ReplSet.prototype.connect with no callback function"); |
| 172 | ||
| 173 | 0 | var self = this; |
| 174 | // Save db reference | |
| 175 | 0 | this.options.db = parent; |
| 176 | // Set replicaset as connecting | |
| 177 | 0 | this._serverState = ReplSet.REPLSET_CONNECTING |
| 178 | // Copy all the servers to our list of seeds | |
| 179 | 0 | var candidateServers = this.servers.slice(0); |
| 180 | // Pop the first server | |
| 181 | 0 | var server = candidateServers.pop(); |
| 182 | 0 | server.name = format("%s:%s", server.host, server.port); |
| 183 | // Set up the options | |
| 184 | 0 | var opts = { |
| 185 | returnIsMasterResults: true, | |
| 186 | eventReceiver: server | |
| 187 | } | |
| 188 | ||
| 189 | // Register some event listeners | |
| 190 | 0 | this.once("fullsetup", function(err, db, replset) { |
| 191 | // Set state to connected | |
| 192 | 0 | self._serverState = ReplSet.REPLSET_CONNECTED; |
| 193 | // Stop any process running | |
| 194 | 0 | if(self._haProcess) self._haProcess.stop(); |
| 195 | // Start the HA process | |
| 196 | 0 | self._haProcess.start(); |
| 197 | ||
| 198 | // Emit fullsetup | |
| 199 | 0 | processor(function() { |
| 200 | 0 | if(self.emitOpen) |
| 201 | 0 | self._emitAcrossAllDbInstances(self, null, "open", null, null, null); |
| 202 | ||
| 203 | 0 | self._emitAcrossAllDbInstances(self, null, "fullsetup", null, null, null); |
| 204 | }); | |
| 205 | ||
| 206 | // If we have a strategy defined start it | |
| 207 | 0 | if(self.strategyInstance) { |
| 208 | 0 | self.strategyInstance.start(); |
| 209 | } | |
| 210 | ||
| 211 | // Finishing up the call | |
| 212 | 0 | callback(err, db, replset); |
| 213 | }); | |
| 214 | ||
| 215 | // Errors | |
| 216 | 0 | this.once("connectionError", function(err, result) { |
| 217 | 0 | callback(err, result); |
| 218 | }); | |
| 219 | ||
| 220 | // Attempt to connect to the server | |
| 221 | 0 | server.connect(this.options.db, opts, _connectHandler(this, candidateServers, server)); |
| 222 | } | |
| 223 | ||
| 224 | 1 | ReplSet.prototype.close = function(callback) { |
| 225 | 0 | var self = this; |
| 226 | // Set as destroyed | |
| 227 | 0 | this._serverState = ReplSet.REPLSET_DESTROYED; |
| 228 | // Stop the ha | |
| 229 | 0 | this._haProcess.stop(); |
| 230 | ||
| 231 | // If we have a strategy stop it | |
| 232 | 0 | if(this.strategyInstance) { |
| 233 | 0 | this.strategyInstance.stop(); |
| 234 | } | |
| 235 | ||
| 236 | // Kill all servers available | |
| 237 | 0 | for(var name in this._state.addresses) { |
| 238 | 0 | this._state.addresses[name].close(); |
| 239 | } | |
| 240 | ||
| 241 | // Clean out the state | |
| 242 | 0 | this._state = new ReplSetState(this); |
| 243 | ||
| 244 | // Emit close event | |
| 245 | 0 | processor(function() { |
| 246 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 247 | }); | |
| 248 | ||
| 249 | // Flush out any remaining call handlers | |
| 250 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 251 | ||
| 252 | // Callback | |
| 253 | 0 | if(typeof callback == 'function') |
| 254 | 0 | return callback(null, null); |
| 255 | } | |
| 256 | ||
| 257 | /** | |
| 258 | * Creates a new server for the `replset` based on `host`. | |
| 259 | * | |
| 260 | * @param {String} host - host:port pair (localhost:27017) | |
| 261 | * @param {ReplSet} replset - the ReplSet instance | |
| 262 | * @return {Server} | |
| 263 | * @ignore | |
| 264 | */ | |
| 265 | 1 | var createServer = function(self, host, options) { |
| 266 | // copy existing socket options to new server | |
| 267 | 0 | var socketOptions = {} |
| 268 | 0 | if(options.socketOptions) { |
| 269 | 0 | var keys = Object.keys(options.socketOptions); |
| 270 | 0 | for(var k = 0; k < keys.length; k++) { |
| 271 | 0 | socketOptions[keys[k]] = options.socketOptions[keys[k]]; |
| 272 | } | |
| 273 | } | |
| 274 | ||
| 275 | 0 | var parts = host.split(/:/); |
| 276 | 0 | if(1 === parts.length) { |
| 277 | 0 | parts[1] = Connection.DEFAULT_PORT; |
| 278 | } | |
| 279 | ||
| 280 | 0 | socketOptions.host = parts[0]; |
| 281 | 0 | socketOptions.port = parseInt(parts[1], 10); |
| 282 | ||
| 283 | 0 | var serverOptions = { |
| 284 | readPreference: options.readPreference, | |
| 285 | socketOptions: socketOptions, | |
| 286 | poolSize: options.poolSize, | |
| 287 | logger: options.logger, | |
| 288 | auto_reconnect: false, | |
| 289 | ssl: options.ssl, | |
| 290 | sslValidate: options.sslValidate, | |
| 291 | sslCA: options.sslCA, | |
| 292 | sslCert: options.sslCert, | |
| 293 | sslKey: options.sslKey, | |
| 294 | sslPass: options.sslPass | |
| 295 | } | |
| 296 | ||
| 297 | 0 | var server = new Server(socketOptions.host, socketOptions.port, serverOptions); |
| 298 | // Set up shared state | |
| 299 | 0 | server._callBackStore = self._callBackStore; |
| 300 | 0 | server.replicasetInstance = self; |
| 301 | 0 | server.enableRecordQueryStats(self.recordQueryStats); |
| 302 | // Set up event handlers | |
| 303 | 0 | server.on("close", _handler("close", self, server)); |
| 304 | 0 | server.on("error", _handler("error", self, server)); |
| 305 | 0 | server.on("timeout", _handler("timeout", self, server)); |
| 306 | 0 | return server; |
| 307 | } | |
| 308 | ||
| 309 | 1 | var _handler = function(event, self, server) { |
| 310 | 0 | return function(err, doc) { |
| 311 | // The event happened to a primary | |
| 312 | // Remove it from play | |
| 313 | 0 | if(self._state.isPrimary(server)) { |
| 314 | // Emit that the primary left the replicaset | |
| 315 | 0 | self.emit('left', 'primary', server); |
| 316 | // Get the current master | |
| 317 | 0 | var current_master = self._state.master; |
| 318 | 0 | self._state.master = null; |
| 319 | 0 | self._serverState = ReplSet.REPLSET_READ_ONLY; |
| 320 | ||
| 321 | 0 | if(current_master != null) { |
| 322 | // Unpack variables | |
| 323 | 0 | var host = current_master.socketOptions.host; |
| 324 | 0 | var port = current_master.socketOptions.port; |
| 325 | ||
| 326 | // Fire error on any unknown callbacks | |
| 327 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 328 | } | |
| 329 | 0 | } else if(self._state.isSecondary(server)) { |
| 330 | // Emit that a secondary left the replicaset | |
| 331 | 0 | self.emit('left', 'secondary', server); |
| 332 | // Delete from the list | |
| 333 | 0 | delete self._state.secondaries[server.name]; |
| 334 | } | |
| 335 | ||
| 336 | // If there is no more connections left and the setting is not destroyed | |
| 337 | // set to disconnected | |
| 338 | 0 | if(Object.keys(self._state.addresses).length == 0 |
| 339 | && self._serverState != ReplSet.REPLSET_DESTROYED) { | |
| 340 | 0 | self._serverState = ReplSet.REPLSET_DISCONNECTED; |
| 341 | ||
| 342 | // Emit close across all the attached db instances | |
| 343 | 0 | self._dbStore.emit("close", new Error("replicaset disconnected, no valid servers contactable over tcp"), null, true); |
| 344 | } | |
| 345 | ||
| 346 | // Unpack variables | |
| 347 | 0 | var host = server.socketOptions.host; |
| 348 | 0 | var port = server.socketOptions.port; |
| 349 | ||
| 350 | // Fire error on any unknown callbacks | |
| 351 | 0 | self.__executeAllServerSpecificErrorCallbacks(host, port, err); |
| 352 | } | |
| 353 | } | |
| 354 | ||
| 355 | 1 | var locateNewServers = function(self, state, candidateServers, ismaster) { |
| 356 | // Retrieve the host | |
| 357 | 0 | var hosts = ismaster.hosts; |
| 358 | // In candidate servers | |
| 359 | 0 | var inCandidateServers = function(name, candidateServers) { |
| 360 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 361 | 0 | if(candidateServers[i].name == name) return true; |
| 362 | } | |
| 363 | ||
| 364 | 0 | return false; |
| 365 | } | |
| 366 | ||
| 367 | // New servers | |
| 368 | 0 | var newServers = []; |
| 369 | 0 | if(Array.isArray(hosts)) { |
| 370 | // Let's go over all the hosts | |
| 371 | 0 | for(var i = 0; i < hosts.length; i++) { |
| 372 | 0 | if(!state.contains(hosts[i]) |
| 373 | && !inCandidateServers(hosts[i], candidateServers)) { | |
| 374 | 0 | newServers.push(createServer(self, hosts[i], self.options)); |
| 375 | } | |
| 376 | } | |
| 377 | } | |
| 378 | ||
| 379 | // Return list of possible new servers | |
| 380 | 0 | return newServers; |
| 381 | } | |
| 382 | ||
| 383 | 1 | var _connectHandler = function(self, candidateServers, instanceServer) { |
| 384 | 0 | return function(err, doc) { |
| 385 | // If we have an error add to the list | |
| 386 | 0 | if(err) { |
| 387 | 0 | self._state.errors[instanceServer.name] = instanceServer; |
| 388 | } else { | |
| 389 | 0 | delete self._state.errors[instanceServer.name]; |
| 390 | } | |
| 391 | ||
| 392 | 0 | if(!err) { |
| 393 | 0 | var ismaster = doc.documents[0] |
| 394 | ||
| 395 | // Error the server if | |
| 396 | 0 | if(!ismaster.ismaster |
| 397 | && !ismaster.secondary) { | |
| 398 | 0 | self._state.errors[instanceServer.name] = instanceServer; |
| 399 | } | |
| 400 | } | |
| 401 | ||
| 402 | ||
| 403 | // No error let's analyse the ismaster command | |
| 404 | 0 | if(!err && self._state.errors[instanceServer.name] == null) { |
| 405 | 0 | var ismaster = doc.documents[0] |
| 406 | ||
| 407 | // If no replicaset name exists set the current one | |
| 408 | 0 | if(self.options.rs_name == null) { |
| 409 | 0 | self.options.rs_name = ismaster.setName; |
| 410 | } | |
| 411 | ||
| 412 | // If we have a member that is not part of the set let's finish up | |
| 413 | 0 | if(typeof ismaster.setName == 'string' && ismaster.setName != self.options.rs_name) { |
| 414 | 0 | return self.emit("connectionError", new Error("Replicaset name " + ismaster.setName + " does not match specified name " + self.options.rs_name)); |
| 415 | } | |
| 416 | ||
| 417 | // Add the error handlers | |
| 418 | 0 | instanceServer.on("close", _handler("close", self, instanceServer)); |
| 419 | 0 | instanceServer.on("error", _handler("error", self, instanceServer)); |
| 420 | 0 | instanceServer.on("timeout", _handler("timeout", self, instanceServer)); |
| 421 | ||
| 422 | // Set any tags on the instance server | |
| 423 | 0 | instanceServer.name = ismaster.me; |
| 424 | 0 | instanceServer.tags = ismaster.tags; |
| 425 | ||
| 426 | // Add the server to the list | |
| 427 | 0 | self._state.addServer(instanceServer, ismaster); |
| 428 | ||
| 429 | // Check if we have more servers to add (only check when done with initial set) | |
| 430 | 0 | if(candidateServers.length == 0) { |
| 431 | // Get additional new servers that are not currently in set | |
| 432 | 0 | var new_servers = locateNewServers(self, self._state, candidateServers, ismaster); |
| 433 | ||
| 434 | // Locate any new servers that have not errored out yet | |
| 435 | 0 | for(var i = 0; i < new_servers.length; i++) { |
| 436 | 0 | if(self._state.errors[new_servers[i].name] == null) { |
| 437 | 0 | candidateServers.push(new_servers[i]) |
| 438 | } | |
| 439 | } | |
| 440 | } | |
| 441 | } | |
| 442 | ||
| 443 | // If the candidate server list is empty and no valid servers | |
| 444 | 0 | if(candidateServers.length == 0 && |
| 445 | !self._state.hasValidServers()) { | |
| 446 | 0 | return self.emit("connectionError", new Error("No valid replicaset instance servers found")); |
| 447 | 0 | } else if(candidateServers.length == 0) { |
| 448 | 0 | if(!self.options.connectWithNoPrimary && (self._state.master == null || !self._state.master.isConnected())) { |
| 449 | 0 | return self.emit("connectionError", new Error("No primary found in set")); |
| 450 | } | |
| 451 | 0 | return self.emit("fullsetup", null, self.options.db, self); |
| 452 | } | |
| 453 | ||
| 454 | // Let's connect the next server | |
| 455 | 0 | var nextServer = candidateServers.pop(); |
| 456 | ||
| 457 | // Set up the options | |
| 458 | 0 | var opts = { |
| 459 | returnIsMasterResults: true, | |
| 460 | eventReceiver: nextServer | |
| 461 | } | |
| 462 | ||
| 463 | // Attempt to connect to the server | |
| 464 | 0 | nextServer.connect(self.options.db, opts, _connectHandler(self, candidateServers, nextServer)); |
| 465 | } | |
| 466 | } | |
| 467 | ||
| 468 | 1 | ReplSet.prototype.isDestroyed = function() { |
| 469 | 0 | return this._serverState == ReplSet.REPLSET_DESTROYED; |
| 470 | } | |
| 471 | ||
| 472 | 1 | ReplSet.prototype.isConnected = function(read) { |
| 473 | 0 | var isConnected = false; |
| 474 | ||
| 475 | 0 | if(read == null || read == ReadPreference.PRIMARY || read == false) |
| 476 | 0 | isConnected = this._state.master != null && this._state.master.isConnected(); |
| 477 | ||
| 478 | 0 | if((read == ReadPreference.PRIMARY_PREFERRED || read == ReadPreference.SECONDARY_PREFERRED || read == ReadPreference.NEAREST) |
| 479 | && ((this._state.master != null && this._state.master.isConnected()) | |
| 480 | || (this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0))) { | |
| 481 | 0 | isConnected = true; |
| 482 | 0 | } else if(read == ReadPreference.SECONDARY) { |
| 483 | 0 | isConnected = this._state && this._state.secondaries && Object.keys(this._state.secondaries).length > 0; |
| 484 | } | |
| 485 | ||
| 486 | // No valid connection return false | |
| 487 | 0 | return isConnected; |
| 488 | } | |
| 489 | ||
| 490 | 1 | ReplSet.prototype.isMongos = function() { |
| 491 | 0 | return false; |
| 492 | } | |
| 493 | ||
| 494 | 1 | ReplSet.prototype.checkoutWriter = function() { |
| 495 | 0 | if(this._state.master) return this._state.master.checkoutWriter(); |
| 496 | 0 | return new Error("no writer connection available"); |
| 497 | } | |
| 498 | ||
| 499 | 1 | ReplSet.prototype.processIsMaster = function(_server, _ismaster) { |
| 500 | // Server in recovery mode, remove it from available servers | |
| 501 | 0 | if(!_ismaster.ismaster && !_ismaster.secondary) { |
| 502 | // Locate the actual server | |
| 503 | 0 | var server = this._state.addresses[_server.name]; |
| 504 | // Close the server, simulating the closing of the connection | |
| 505 | // to get right removal semantics | |
| 506 | 0 | if(server) server.close(); |
| 507 | // Execute any callback errors | |
| 508 | 0 | _handler(null, this, server)(new Error("server is in recovery mode")); |
| 509 | } | |
| 510 | } | |
| 511 | ||
| 512 | 1 | ReplSet.prototype.allRawConnections = function() { |
| 513 | 0 | var connections = []; |
| 514 | ||
| 515 | 0 | for(var name in this._state.addresses) { |
| 516 | 0 | connections = connections.concat(this._state.addresses[name].allRawConnections()); |
| 517 | } | |
| 518 | ||
| 519 | 0 | return connections; |
| 520 | } | |
| 521 | ||
| 522 | /** | |
| 523 | * @ignore | |
| 524 | */ | |
| 525 | 1 | ReplSet.prototype.allServerInstances = function() { |
| 526 | 0 | var self = this; |
| 527 | // If no state yet return empty | |
| 528 | 0 | if(!self._state) return []; |
| 529 | // Close all the servers (concatenate entire list of servers first for ease) | |
| 530 | 0 | var allServers = self._state.master != null ? [self._state.master] : []; |
| 531 | ||
| 532 | // Secondary keys | |
| 533 | 0 | var keys = Object.keys(self._state.secondaries); |
| 534 | // Add all secondaries | |
| 535 | 0 | for(var i = 0; i < keys.length; i++) { |
| 536 | 0 | allServers.push(self._state.secondaries[keys[i]]); |
| 537 | } | |
| 538 | ||
| 539 | // Return complete list of all servers | |
| 540 | 0 | return allServers; |
| 541 | } | |
| 542 | ||
| 543 | /** | |
| 544 | * @ignore | |
| 545 | */ | |
| 546 | 1 | ReplSet.prototype.checkoutReader = function(readPreference, tags) { |
| 547 | 0 | var connection = null; |
| 548 | ||
| 549 | // If we have a read preference object unpack it | |
| 550 | 0 | if(typeof readPreference == 'object' && readPreference['_type'] == 'ReadPreference') { |
| 551 | // Validate if the object is using a valid mode | |
| 552 | 0 | if(!readPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + readPreference.mode); |
| 553 | // Set the tag | |
| 554 | 0 | tags = readPreference.tags; |
| 555 | 0 | readPreference = readPreference.mode; |
| 556 | 0 | } else if(typeof readPreference == 'object' && readPreference['_type'] != 'ReadPreference') { |
| 557 | 0 | return new Error("read preferences must be either a string or an instance of ReadPreference"); |
| 558 | } | |
| 559 | ||
| 560 | // Set up our read Preference, allowing us to override the readPreference | |
| 561 | 0 | var finalReadPreference = readPreference != null ? readPreference : this.options.readPreference; |
| 562 | ||
| 563 | // Ensure we unpack a reference | |
| 564 | 0 | if(finalReadPreference != null && typeof finalReadPreference == 'object' && finalReadPreference['_type'] == 'ReadPreference') { |
| 565 | // Validate if the object is using a valid mode | |
| 566 | 0 | if(!finalReadPreference.isValid()) throw new Error("Illegal readPreference mode specified, " + finalReadPreference.mode); |
| 567 | // Set the tag | |
| 568 | 0 | tags = finalReadPreference.tags; |
| 569 | 0 | readPreference = finalReadPreference.mode; |
| 570 | } | |
| 571 | ||
| 572 | // Finalize the read preference setup | |
| 573 | 0 | finalReadPreference = finalReadPreference == true ? ReadPreference.SECONDARY_PREFERRED : finalReadPreference; |
| 574 | 0 | finalReadPreference = finalReadPreference == null ? ReadPreference.PRIMARY : finalReadPreference; |
| 575 | ||
| 576 | // If we are reading from a primary | |
| 577 | 0 | if(finalReadPreference == 'primary') { |
| 578 | // If we provide a tags set send an error | |
| 579 | 0 | if(typeof tags == 'object' && tags != null) { |
| 580 | 0 | return new Error("PRIMARY cannot be combined with tags"); |
| 581 | } | |
| 582 | ||
| 583 | // If we provide a tags set send an error | |
| 584 | 0 | if(this._state.master == null) { |
| 585 | 0 | return new Error("No replica set primary available for query with ReadPreference PRIMARY"); |
| 586 | } | |
| 587 | ||
| 588 | // Checkout a writer | |
| 589 | 0 | return this.checkoutWriter(); |
| 590 | } | |
| 591 | ||
| 592 | // If we have specified to read from a secondary server grab a random one and read | |
| 593 | // from it, otherwise just pass the primary connection | |
| 594 | 0 | if((this.options.readSecondary || finalReadPreference == ReadPreference.SECONDARY_PREFERRED || finalReadPreference == ReadPreference.SECONDARY) && Object.keys(this._state.secondaries).length > 0) { |
| 595 | // If we have tags, look for servers matching the specific tag | |
| 596 | 0 | if(this.strategyInstance != null) { |
| 597 | // Only pick from secondaries | |
| 598 | 0 | var _secondaries = []; |
| 599 | 0 | for(var key in this._state.secondaries) { |
| 600 | 0 | _secondaries.push(this._state.secondaries[key]); |
| 601 | } | |
| 602 | ||
| 603 | 0 | if(finalReadPreference == ReadPreference.SECONDARY) { |
| 604 | // Check out the nearest from only the secondaries | |
| 605 | 0 | connection = this.strategyInstance.checkoutConnection(tags, _secondaries); |
| 606 | } else { | |
| 607 | 0 | connection = this.strategyInstance.checkoutConnection(tags, _secondaries); |
| 608 | // No candidate servers that match the tags, error | |
| 609 | 0 | if(connection == null || connection instanceof Error) { |
| 610 | // No secondary server avilable, attemp to checkout a primary server | |
| 611 | 0 | connection = this.checkoutWriter(); |
| 612 | // If no connection return an error | |
| 613 | 0 | if(connection == null || connection instanceof Error) { |
| 614 | 0 | return new Error("No replica set members available for query"); |
| 615 | } | |
| 616 | } | |
| 617 | } | |
| 618 | 0 | } else if(tags != null && typeof tags == 'object') { |
| 619 | // Get connection | |
| 620 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 621 | // No candidate servers that match the tags, error | |
| 622 | 0 | if(connection == null) { |
| 623 | 0 | return new Error("No replica set members available for query"); |
| 624 | } | |
| 625 | } else { | |
| 626 | 0 | connection = _roundRobin(this, tags); |
| 627 | } | |
| 628 | 0 | } else if(finalReadPreference == ReadPreference.PRIMARY_PREFERRED) { |
| 629 | // Check if there is a primary available and return that if possible | |
| 630 | 0 | connection = this.checkoutWriter(); |
| 631 | // If no connection available checkout a secondary | |
| 632 | 0 | if(connection == null || connection instanceof Error) { |
| 633 | // If we have tags, look for servers matching the specific tag | |
| 634 | 0 | if(tags != null && typeof tags == 'object') { |
| 635 | // Get connection | |
| 636 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 637 | // No candidate servers that match the tags, error | |
| 638 | 0 | if(connection == null) { |
| 639 | 0 | return new Error("No replica set members available for query"); |
| 640 | } | |
| 641 | } else { | |
| 642 | 0 | connection = _roundRobin(this, tags); |
| 643 | } | |
| 644 | } | |
| 645 | 0 | } else if(finalReadPreference == ReadPreference.SECONDARY_PREFERRED) { |
| 646 | // If we have tags, look for servers matching the specific tag | |
| 647 | 0 | if(this.strategyInstance != null) { |
| 648 | 0 | connection = this.strategyInstance.checkoutConnection(tags); |
| 649 | ||
| 650 | // No candidate servers that match the tags, error | |
| 651 | 0 | if(connection == null || connection instanceof Error) { |
| 652 | // No secondary server avilable, attemp to checkout a primary server | |
| 653 | 0 | connection = this.checkoutWriter(); |
| 654 | // If no connection return an error | |
| 655 | 0 | if(connection == null || connection instanceof Error) { |
| 656 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 657 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 658 | } | |
| 659 | } | |
| 660 | 0 | } else if(tags != null && typeof tags == 'object') { |
| 661 | // Get connection | |
| 662 | 0 | connection = _pickFromTags(this, tags);// = function(self, readPreference, tags) { |
| 663 | // No candidate servers that match the tags, error | |
| 664 | 0 | if(connection == null) { |
| 665 | // No secondary server avilable, attemp to checkout a primary server | |
| 666 | 0 | connection = this.checkoutWriter(); |
| 667 | // If no connection return an error | |
| 668 | 0 | if(connection == null || connection instanceof Error) { |
| 669 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 670 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 671 | } | |
| 672 | } | |
| 673 | } | |
| 674 | 0 | } else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance != null) { |
| 675 | 0 | connection = this.strategyInstance.checkoutConnection(tags); |
| 676 | 0 | } else if(finalReadPreference == ReadPreference.NEAREST && this.strategyInstance == null) { |
| 677 | 0 | return new Error("A strategy for calculating nearness must be enabled such as ping or statistical"); |
| 678 | 0 | } else if(finalReadPreference == ReadPreference.SECONDARY && Object.keys(this._state.secondaries).length == 0) { |
| 679 | 0 | if(tags != null && typeof tags == 'object') { |
| 680 | 0 | var preferenceName = finalReadPreference == ReadPreference.SECONDARY ? 'secondary' : finalReadPreference; |
| 681 | 0 | return new Error("No replica set member available for query with ReadPreference " + preferenceName + " and tags " + JSON.stringify(tags)); |
| 682 | } else { | |
| 683 | 0 | return new Error("No replica set secondary available for query with ReadPreference SECONDARY"); |
| 684 | } | |
| 685 | } else { | |
| 686 | 0 | connection = this.checkoutWriter(); |
| 687 | } | |
| 688 | ||
| 689 | // Return the connection | |
| 690 | 0 | return connection; |
| 691 | } | |
| 692 | ||
| 693 | /** | |
| 694 | * @ignore | |
| 695 | */ | |
| 696 | 1 | var _pickFromTags = function(self, tags) { |
| 697 | // If we have an array or single tag selection | |
| 698 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 699 | // Iterate over all tags until we find a candidate server | |
| 700 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 701 | // Grab a tag object | |
| 702 | 0 | var tagObject = tagObjects[_i]; |
| 703 | // Matching keys | |
| 704 | 0 | var matchingKeys = Object.keys(tagObject); |
| 705 | // Match all the servers that match the provdided tags | |
| 706 | 0 | var keys = Object.keys(self._state.secondaries); |
| 707 | 0 | var candidateServers = []; |
| 708 | ||
| 709 | 0 | for(var i = 0; i < keys.length; i++) { |
| 710 | 0 | var server = self._state.secondaries[keys[i]]; |
| 711 | // If we have tags match | |
| 712 | 0 | if(server.tags != null) { |
| 713 | 0 | var matching = true; |
| 714 | // Ensure we have all the values | |
| 715 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 716 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 717 | 0 | matching = false; |
| 718 | 0 | break; |
| 719 | } | |
| 720 | } | |
| 721 | ||
| 722 | // If we have a match add it to the list of matching servers | |
| 723 | 0 | if(matching) { |
| 724 | 0 | candidateServers.push(server); |
| 725 | } | |
| 726 | } | |
| 727 | } | |
| 728 | ||
| 729 | // If we have a candidate server return | |
| 730 | 0 | if(candidateServers.length > 0) { |
| 731 | 0 | if(self.strategyInstance) return self.strategyInstance.checkoutConnection(tags, candidateServers); |
| 732 | // Set instance to return | |
| 733 | 0 | return candidateServers[Math.floor(Math.random() * candidateServers.length)].checkoutReader(); |
| 734 | } | |
| 735 | } | |
| 736 | ||
| 737 | // No connection found | |
| 738 | 0 | return null; |
| 739 | } | |
| 740 | ||
| 741 | /** | |
| 742 | * Pick a secondary using round robin | |
| 743 | * | |
| 744 | * @ignore | |
| 745 | */ | |
| 746 | 1 | function _roundRobin (replset, tags) { |
| 747 | 0 | var keys = Object.keys(replset._state.secondaries); |
| 748 | // Update index | |
| 749 | 0 | replset._currentServerChoice = replset._currentServerChoice + 1; |
| 750 | // Pick a server | |
| 751 | 0 | var key = keys[replset._currentServerChoice % keys.length]; |
| 752 | ||
| 753 | 0 | var conn = null != replset._state.secondaries[key] |
| 754 | ? replset._state.secondaries[key].checkoutReader() | |
| 755 | : null; | |
| 756 | ||
| 757 | // If connection is null fallback to first available secondary | |
| 758 | 0 | if(null == conn) { |
| 759 | 0 | conn = pickFirstConnectedSecondary(replset, tags); |
| 760 | } | |
| 761 | ||
| 762 | 0 | return conn; |
| 763 | } | |
| 764 | ||
| 765 | /** | |
| 766 | * @ignore | |
| 767 | */ | |
| 768 | 1 | var pickFirstConnectedSecondary = function pickFirstConnectedSecondary(self, tags) { |
| 769 | 0 | var keys = Object.keys(self._state.secondaries); |
| 770 | 0 | var connection; |
| 771 | ||
| 772 | // Find first available reader if any | |
| 773 | 0 | for(var i = 0; i < keys.length; i++) { |
| 774 | 0 | connection = self._state.secondaries[keys[i]].checkoutReader(); |
| 775 | 0 | if(connection) return connection; |
| 776 | } | |
| 777 | ||
| 778 | // If we still have a null, read from primary if it's not secondary only | |
| 779 | 0 | if(self._readPreference == ReadPreference.SECONDARY_PREFERRED) { |
| 780 | 0 | connection = self._state.master.checkoutReader(); |
| 781 | 0 | if(connection) return connection; |
| 782 | } | |
| 783 | ||
| 784 | 0 | var preferenceName = self._readPreference == ReadPreference.SECONDARY_PREFERRED |
| 785 | ? 'secondary' | |
| 786 | : self._readPreference; | |
| 787 | ||
| 788 | 0 | return new Error("No replica set member available for query with ReadPreference " |
| 789 | + preferenceName + " and tags " + JSON.stringify(tags)); | |
| 790 | } | |
| 791 | ||
| 792 | /** | |
| 793 | * Get list of secondaries | |
| 794 | * @ignore | |
| 795 | */ | |
| 796 | 1 | Object.defineProperty(ReplSet.prototype, "secondaries", {enumerable: true |
| 797 | , get: function() { | |
| 798 | 0 | return utils.objectToArray(this._state.secondaries); |
| 799 | } | |
| 800 | }); | |
| 801 | ||
| 802 | /** | |
| 803 | * Get list of secondaries | |
| 804 | * @ignore | |
| 805 | */ | |
| 806 | 1 | Object.defineProperty(ReplSet.prototype, "arbiters", {enumerable: true |
| 807 | , get: function() { | |
| 808 | 0 | return utils.objectToArray(this._state.arbiters); |
| 809 | } | |
| 810 | }); | |
| 811 | ||
| 812 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Interval state object constructor | |
| 3 | * | |
| 4 | * @ignore | |
| 5 | */ | |
| 6 | 1 | var ReplSetState = function ReplSetState (replset) { |
| 7 | 0 | this.errorMessages = []; |
| 8 | 0 | this.secondaries = {}; |
| 9 | 0 | this.addresses = {}; |
| 10 | 0 | this.arbiters = {}; |
| 11 | 0 | this.passives = {}; |
| 12 | 0 | this.members = []; |
| 13 | 0 | this.errors = {}; |
| 14 | 0 | this.setName = null; |
| 15 | 0 | this.master = null; |
| 16 | 0 | this.replset = replset; |
| 17 | } | |
| 18 | ||
| 19 | 1 | ReplSetState.prototype.hasValidServers = function() { |
| 20 | 0 | var validServers = []; |
| 21 | 0 | if(this.master && this.master.isConnected()) return true; |
| 22 | ||
| 23 | 0 | if(this.secondaries) { |
| 24 | 0 | var keys = Object.keys(this.secondaries) |
| 25 | 0 | for(var i = 0; i < keys.length; i++) { |
| 26 | 0 | if(this.secondaries[keys[i]].isConnected()) |
| 27 | 0 | return true; |
| 28 | } | |
| 29 | } | |
| 30 | ||
| 31 | 0 | return false; |
| 32 | } | |
| 33 | ||
| 34 | 1 | ReplSetState.prototype.getAllReadServers = function() { |
| 35 | 0 | var candidate_servers = []; |
| 36 | 0 | for(var name in this.addresses) { |
| 37 | 0 | candidate_servers.push(this.addresses[name]); |
| 38 | } | |
| 39 | ||
| 40 | // Return all possible read candidates | |
| 41 | 0 | return candidate_servers; |
| 42 | } | |
| 43 | ||
| 44 | 1 | ReplSetState.prototype.addServer = function(server, master) { |
| 45 | 0 | server.name = master.me; |
| 46 | ||
| 47 | 0 | if(master.ismaster) { |
| 48 | 0 | this.master = server; |
| 49 | 0 | this.addresses[server.name] = server; |
| 50 | 0 | this.replset.emit('joined', "primary", master, server); |
| 51 | 0 | } else if(master.secondary) { |
| 52 | 0 | this.secondaries[server.name] = server; |
| 53 | 0 | this.addresses[server.name] = server; |
| 54 | 0 | this.replset.emit('joined', "secondary", master, server); |
| 55 | 0 | } else if(master.arbiters) { |
| 56 | 0 | this.arbiters[server.name] = server; |
| 57 | 0 | this.addresses[server.name] = server; |
| 58 | 0 | this.replset.emit('joined', "arbiter", master, server); |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | 1 | ReplSetState.prototype.contains = function(host) { |
| 63 | 0 | return this.addresses[host] != null; |
| 64 | } | |
| 65 | ||
| 66 | 1 | ReplSetState.prototype.isPrimary = function(server) { |
| 67 | 0 | return this.master && this.master.name == server.name; |
| 68 | } | |
| 69 | ||
| 70 | 1 | ReplSetState.prototype.isSecondary = function(server) { |
| 71 | 0 | return this.secondaries[server.name] != null; |
| 72 | } | |
| 73 | ||
| 74 | 1 | exports.ReplSetState = ReplSetState; |
| 75 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Server = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/../../server").Server |
| 2 | , format = require('util').format; | |
| 3 | ||
| 4 | // The ping strategy uses pings each server and records the | |
| 5 | // elapsed time for the server so it can pick a server based on lowest | |
| 6 | // return time for the db command {ping:true} | |
| 7 | 1 | var PingStrategy = exports.PingStrategy = function(replicaset, secondaryAcceptableLatencyMS) { |
| 8 | 0 | this.replicaset = replicaset; |
| 9 | 0 | this.secondaryAcceptableLatencyMS = secondaryAcceptableLatencyMS; |
| 10 | 0 | this.state = 'disconnected'; |
| 11 | 0 | this.pingInterval = 5000; |
| 12 | // Class instance | |
| 13 | 0 | this.Db = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/lib/mongodb/connection/repl_set/strategies/../../../db").Db; |
| 14 | // Active db connections | |
| 15 | 0 | this.dbs = {}; |
| 16 | // Current server index | |
| 17 | 0 | this.index = 0; |
| 18 | // Logger api | |
| 19 | 0 | this.Logger = null; |
| 20 | } | |
| 21 | ||
| 22 | // Starts any needed code | |
| 23 | 1 | PingStrategy.prototype.start = function(callback) { |
| 24 | // already running? | |
| 25 | 0 | if ('connected' == this.state) return; |
| 26 | ||
| 27 | 0 | this.state = 'connected'; |
| 28 | ||
| 29 | // Start ping server | |
| 30 | 0 | this._pingServer(callback); |
| 31 | } | |
| 32 | ||
| 33 | // Stops and kills any processes running | |
| 34 | 1 | PingStrategy.prototype.stop = function(callback) { |
| 35 | // Stop the ping process | |
| 36 | 0 | this.state = 'disconnected'; |
| 37 | ||
| 38 | // Stop all the server instances | |
| 39 | 0 | for(var key in this.dbs) { |
| 40 | 0 | this.dbs[key].close(); |
| 41 | } | |
| 42 | ||
| 43 | // optional callback | |
| 44 | 0 | callback && callback(null, null); |
| 45 | } | |
| 46 | ||
| 47 | 1 | PingStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) { |
| 48 | // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS | |
| 49 | // Create a list of candidat servers, containing the primary if available | |
| 50 | 0 | var candidateServers = []; |
| 51 | 0 | var self = this; |
| 52 | ||
| 53 | // If we have not provided a list of candidate servers use the default setup | |
| 54 | 0 | if(!Array.isArray(secondaryCandidates)) { |
| 55 | 0 | candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : []; |
| 56 | // Add all the secondaries | |
| 57 | 0 | var keys = Object.keys(this.replicaset._state.secondaries); |
| 58 | 0 | for(var i = 0; i < keys.length; i++) { |
| 59 | 0 | candidateServers.push(this.replicaset._state.secondaries[keys[i]]) |
| 60 | } | |
| 61 | } else { | |
| 62 | 0 | candidateServers = secondaryCandidates; |
| 63 | } | |
| 64 | ||
| 65 | // Final list of eligable server | |
| 66 | 0 | var finalCandidates = []; |
| 67 | ||
| 68 | // If we have tags filter by tags | |
| 69 | 0 | if(tags != null && typeof tags == 'object') { |
| 70 | // If we have an array or single tag selection | |
| 71 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 72 | // Iterate over all tags until we find a candidate server | |
| 73 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 74 | // Grab a tag object | |
| 75 | 0 | var tagObject = tagObjects[_i]; |
| 76 | // Matching keys | |
| 77 | 0 | var matchingKeys = Object.keys(tagObject); |
| 78 | // Remove any that are not tagged correctly | |
| 79 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 80 | 0 | var server = candidateServers[i]; |
| 81 | // If we have tags match | |
| 82 | 0 | if(server.tags != null) { |
| 83 | 0 | var matching = true; |
| 84 | ||
| 85 | // Ensure we have all the values | |
| 86 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 87 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 88 | 0 | matching = false; |
| 89 | 0 | break; |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | // If we have a match add it to the list of matching servers | |
| 94 | 0 | if(matching) { |
| 95 | 0 | finalCandidates.push(server); |
| 96 | } | |
| 97 | } | |
| 98 | } | |
| 99 | } | |
| 100 | } else { | |
| 101 | // Final array candidates | |
| 102 | 0 | var finalCandidates = candidateServers; |
| 103 | } | |
| 104 | ||
| 105 | // Sort by ping time | |
| 106 | 0 | finalCandidates.sort(function(a, b) { |
| 107 | 0 | return a.runtimeStats['pingMs'] > b.runtimeStats['pingMs']; |
| 108 | }); | |
| 109 | ||
| 110 | 0 | if(0 === finalCandidates.length) |
| 111 | 0 | return new Error("No replica set members available for query"); |
| 112 | ||
| 113 | // find lowest server with a ping time | |
| 114 | 0 | var lowest = finalCandidates.filter(function (server) { |
| 115 | 0 | return undefined != server.runtimeStats.pingMs; |
| 116 | })[0]; | |
| 117 | ||
| 118 | 0 | if(!lowest) { |
| 119 | 0 | lowest = finalCandidates[0]; |
| 120 | } | |
| 121 | ||
| 122 | // convert to integer | |
| 123 | 0 | var lowestPing = lowest.runtimeStats.pingMs | 0; |
| 124 | ||
| 125 | // determine acceptable latency | |
| 126 | 0 | var acceptable = lowestPing + this.secondaryAcceptableLatencyMS; |
| 127 | ||
| 128 | // remove any server responding slower than acceptable | |
| 129 | 0 | var len = finalCandidates.length; |
| 130 | 0 | while(len--) { |
| 131 | 0 | if(finalCandidates[len].runtimeStats['pingMs'] > acceptable) { |
| 132 | 0 | finalCandidates.splice(len, 1); |
| 133 | } | |
| 134 | } | |
| 135 | ||
| 136 | 0 | if(self.logger && self.logger.debug) { |
| 137 | 0 | self.logger.debug("Ping strategy selection order for tags", tags); |
| 138 | 0 | finalCandidates.forEach(function(c) { |
| 139 | 0 | self.logger.debug(format("%s:%s = %s ms", c.host, c.port, c.runtimeStats['pingMs']), null); |
| 140 | }) | |
| 141 | } | |
| 142 | ||
| 143 | // If no candidates available return an error | |
| 144 | 0 | if(finalCandidates.length == 0) |
| 145 | 0 | return new Error("No replica set members available for query"); |
| 146 | ||
| 147 | // Ensure no we don't overflow | |
| 148 | 0 | this.index = this.index % finalCandidates.length |
| 149 | // Pick a random acceptable server | |
| 150 | 0 | var connection = finalCandidates[this.index].checkoutReader(); |
| 151 | // Point to next candidate (round robin style) | |
| 152 | 0 | this.index = this.index + 1; |
| 153 | ||
| 154 | 0 | if(self.logger && self.logger.debug) { |
| 155 | 0 | if(connection) |
| 156 | 0 | self.logger.debug("picked server %s:%s", connection.socketOptions.host, connection.socketOptions.port); |
| 157 | } | |
| 158 | ||
| 159 | 0 | return connection; |
| 160 | } | |
| 161 | ||
| 162 | 1 | PingStrategy.prototype._pingServer = function(callback) { |
| 163 | 0 | var self = this; |
| 164 | ||
| 165 | // Ping server function | |
| 166 | 0 | var pingFunction = function() { |
| 167 | // Our state changed to disconnected or destroyed return | |
| 168 | 0 | if(self.state == 'disconnected' || self.state == 'destroyed') return; |
| 169 | // If the replicaset is destroyed return | |
| 170 | 0 | if(self.replicaset.isDestroyed() || self.replicaset._serverState == 'disconnected') return |
| 171 | ||
| 172 | // Create a list of all servers we can send the ismaster command to | |
| 173 | 0 | var allServers = self.replicaset._state.master != null ? [self.replicaset._state.master] : []; |
| 174 | ||
| 175 | // Secondary keys | |
| 176 | 0 | var keys = Object.keys(self.replicaset._state.secondaries); |
| 177 | // Add all secondaries | |
| 178 | 0 | for(var i = 0; i < keys.length; i++) { |
| 179 | 0 | allServers.push(self.replicaset._state.secondaries[keys[i]]); |
| 180 | } | |
| 181 | ||
| 182 | // Number of server entries | |
| 183 | 0 | var numberOfEntries = allServers.length; |
| 184 | ||
| 185 | // We got keys | |
| 186 | 0 | for(var i = 0; i < allServers.length; i++) { |
| 187 | ||
| 188 | // We got a server instance | |
| 189 | 0 | var server = allServers[i]; |
| 190 | ||
| 191 | // Create a new server object, avoid using internal connections as they might | |
| 192 | // be in an illegal state | |
| 193 | 0 | new function(serverInstance) { |
| 194 | 0 | var _db = self.dbs[serverInstance.host + ":" + serverInstance.port]; |
| 195 | // If we have a db | |
| 196 | 0 | if(_db != null) { |
| 197 | // Startup time of the command | |
| 198 | 0 | var startTime = Date.now(); |
| 199 | ||
| 200 | // Execute ping command in own scope | |
| 201 | 0 | var _ping = function(__db, __serverInstance) { |
| 202 | // Execute ping on this connection | |
| 203 | 0 | __db.executeDbCommand({ping:1}, {failFast:true}, function(err) { |
| 204 | 0 | if(err) { |
| 205 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 206 | 0 | __db.close(); |
| 207 | 0 | return done(); |
| 208 | } | |
| 209 | ||
| 210 | 0 | if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) { |
| 211 | 0 | __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime; |
| 212 | } | |
| 213 | ||
| 214 | 0 | __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) { |
| 215 | 0 | if(err) { |
| 216 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 217 | 0 | __db.close(); |
| 218 | 0 | return done(); |
| 219 | } | |
| 220 | ||
| 221 | // Process the ismaster for the server | |
| 222 | 0 | if(result && result.documents && self.replicaset.processIsMaster) { |
| 223 | 0 | self.replicaset.processIsMaster(__serverInstance, result.documents[0]); |
| 224 | } | |
| 225 | ||
| 226 | // Done with the pinging | |
| 227 | 0 | done(); |
| 228 | }); | |
| 229 | }); | |
| 230 | }; | |
| 231 | // Ping | |
| 232 | 0 | _ping(_db, serverInstance); |
| 233 | } else { | |
| 234 | 0 | var connectTimeoutMS = self.replicaset.options.socketOptions |
| 235 | ? self.replicaset.options.socketOptions.connectTimeoutMS : 0 | |
| 236 | ||
| 237 | // Create a new master connection | |
| 238 | 0 | var _server = new Server(serverInstance.host, serverInstance.port, { |
| 239 | auto_reconnect: false, | |
| 240 | returnIsMasterResults: true, | |
| 241 | slaveOk: true, | |
| 242 | poolSize: 1, | |
| 243 | socketOptions: { connectTimeoutMS: connectTimeoutMS }, | |
| 244 | ssl: self.replicaset.options.ssl, | |
| 245 | sslValidate: self.replicaset.options.sslValidate, | |
| 246 | sslCA: self.replicaset.options.sslCA, | |
| 247 | sslCert: self.replicaset.options.sslCert, | |
| 248 | sslKey: self.replicaset.options.sslKey, | |
| 249 | sslPass: self.replicaset.options.sslPass | |
| 250 | }); | |
| 251 | ||
| 252 | // Create Db instance | |
| 253 | 0 | var _db = new self.Db('local', _server, { safe: true }); |
| 254 | 0 | _db.on("close", function() { |
| 255 | 0 | delete self.dbs[this.serverConfig.host + ":" + this.serverConfig.port]; |
| 256 | }) | |
| 257 | ||
| 258 | 0 | var _ping = function(__db, __serverInstance) { |
| 259 | 0 | if(self.state == 'disconnected') { |
| 260 | 0 | self.stop(); |
| 261 | 0 | return; |
| 262 | } | |
| 263 | ||
| 264 | 0 | __db.open(function(err, db) { |
| 265 | 0 | if(self.state == 'disconnected' && __db != null) { |
| 266 | 0 | return __db.close(); |
| 267 | } | |
| 268 | ||
| 269 | 0 | if(err) { |
| 270 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 271 | 0 | __db.close(); |
| 272 | 0 | return done(); |
| 273 | } | |
| 274 | ||
| 275 | // Save instance | |
| 276 | 0 | self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port] = __db; |
| 277 | ||
| 278 | // Startup time of the command | |
| 279 | 0 | var startTime = Date.now(); |
| 280 | ||
| 281 | // Execute ping on this connection | |
| 282 | 0 | __db.executeDbCommand({ping:1}, {failFast:true}, function(err) { |
| 283 | 0 | if(err) { |
| 284 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 285 | 0 | __db.close(); |
| 286 | 0 | return done(); |
| 287 | } | |
| 288 | ||
| 289 | 0 | if(null != __serverInstance.runtimeStats && __serverInstance.isConnected()) { |
| 290 | 0 | __serverInstance.runtimeStats['pingMs'] = Date.now() - startTime; |
| 291 | } | |
| 292 | ||
| 293 | 0 | __db.executeDbCommand({ismaster:1}, {failFast:true}, function(err, result) { |
| 294 | 0 | if(err) { |
| 295 | 0 | delete self.dbs[__db.serverConfig.host + ":" + __db.serverConfig.port]; |
| 296 | 0 | __db.close(); |
| 297 | 0 | return done(); |
| 298 | } | |
| 299 | ||
| 300 | // Process the ismaster for the server | |
| 301 | 0 | if(result && result.documents && self.replicaset.processIsMaster) { |
| 302 | 0 | self.replicaset.processIsMaster(__serverInstance, result.documents[0]); |
| 303 | } | |
| 304 | ||
| 305 | // Done with the pinging | |
| 306 | 0 | done(); |
| 307 | }); | |
| 308 | }); | |
| 309 | }); | |
| 310 | }; | |
| 311 | ||
| 312 | // Ping the server | |
| 313 | 0 | _ping(_db, serverInstance); |
| 314 | } | |
| 315 | ||
| 316 | 0 | function done() { |
| 317 | // Adjust the number of checks | |
| 318 | 0 | numberOfEntries--; |
| 319 | ||
| 320 | // If we are done with all results coming back trigger ping again | |
| 321 | 0 | if(0 === numberOfEntries && 'connected' == self.state) { |
| 322 | 0 | setTimeout(pingFunction, self.pingInterval); |
| 323 | } | |
| 324 | } | |
| 325 | }(server); | |
| 326 | } | |
| 327 | } | |
| 328 | ||
| 329 | // Start pingFunction | |
| 330 | 0 | pingFunction(); |
| 331 | ||
| 332 | 0 | callback && callback(null); |
| 333 | } | |
| 334 |
| Line | Hits | Source |
|---|---|---|
| 1 | // The Statistics strategy uses the measure of each end-start time for each | |
| 2 | // query executed against the db to calculate the mean, variance and standard deviation | |
| 3 | // and pick the server which the lowest mean and deviation | |
| 4 | 1 | var StatisticsStrategy = exports.StatisticsStrategy = function(replicaset) { |
| 5 | 0 | this.replicaset = replicaset; |
| 6 | // Logger api | |
| 7 | 0 | this.Logger = null; |
| 8 | } | |
| 9 | ||
| 10 | // Starts any needed code | |
| 11 | 1 | StatisticsStrategy.prototype.start = function(callback) { |
| 12 | 0 | callback && callback(null, null); |
| 13 | } | |
| 14 | ||
| 15 | 1 | StatisticsStrategy.prototype.stop = function(callback) { |
| 16 | 0 | callback && callback(null, null); |
| 17 | } | |
| 18 | ||
| 19 | 1 | StatisticsStrategy.prototype.checkoutConnection = function(tags, secondaryCandidates) { |
| 20 | // Servers are picked based on the lowest ping time and then servers that lower than that + secondaryAcceptableLatencyMS | |
| 21 | // Create a list of candidat servers, containing the primary if available | |
| 22 | 0 | var candidateServers = []; |
| 23 | ||
| 24 | // If we have not provided a list of candidate servers use the default setup | |
| 25 | 0 | if(!Array.isArray(secondaryCandidates)) { |
| 26 | 0 | candidateServers = this.replicaset._state.master != null ? [this.replicaset._state.master] : []; |
| 27 | // Add all the secondaries | |
| 28 | 0 | var keys = Object.keys(this.replicaset._state.secondaries); |
| 29 | 0 | for(var i = 0; i < keys.length; i++) { |
| 30 | 0 | candidateServers.push(this.replicaset._state.secondaries[keys[i]]) |
| 31 | } | |
| 32 | } else { | |
| 33 | 0 | candidateServers = secondaryCandidates; |
| 34 | } | |
| 35 | ||
| 36 | // Final list of eligable server | |
| 37 | 0 | var finalCandidates = []; |
| 38 | ||
| 39 | // If we have tags filter by tags | |
| 40 | 0 | if(tags != null && typeof tags == 'object') { |
| 41 | // If we have an array or single tag selection | |
| 42 | 0 | var tagObjects = Array.isArray(tags) ? tags : [tags]; |
| 43 | // Iterate over all tags until we find a candidate server | |
| 44 | 0 | for(var _i = 0; _i < tagObjects.length; _i++) { |
| 45 | // Grab a tag object | |
| 46 | 0 | var tagObject = tagObjects[_i]; |
| 47 | // Matching keys | |
| 48 | 0 | var matchingKeys = Object.keys(tagObject); |
| 49 | // Remove any that are not tagged correctly | |
| 50 | 0 | for(var i = 0; i < candidateServers.length; i++) { |
| 51 | 0 | var server = candidateServers[i]; |
| 52 | // If we have tags match | |
| 53 | 0 | if(server.tags != null) { |
| 54 | 0 | var matching = true; |
| 55 | ||
| 56 | // Ensure we have all the values | |
| 57 | 0 | for(var j = 0; j < matchingKeys.length; j++) { |
| 58 | 0 | if(server.tags[matchingKeys[j]] != tagObject[matchingKeys[j]]) { |
| 59 | 0 | matching = false; |
| 60 | 0 | break; |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | // If we have a match add it to the list of matching servers | |
| 65 | 0 | if(matching) { |
| 66 | 0 | finalCandidates.push(server); |
| 67 | } | |
| 68 | } | |
| 69 | } | |
| 70 | } | |
| 71 | } else { | |
| 72 | // Final array candidates | |
| 73 | 0 | var finalCandidates = candidateServers; |
| 74 | } | |
| 75 | ||
| 76 | // If no candidates available return an error | |
| 77 | 0 | if(finalCandidates.length == 0) return new Error("No replica set members available for query"); |
| 78 | // Pick a random server | |
| 79 | 0 | return finalCandidates[Math.round(Math.random(1000000) * (finalCandidates.length - 1))].checkoutReader(); |
| 80 | } | |
| 81 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Connection = require('./connection').Connection, |
| 2 | ReadPreference = require('./read_preference').ReadPreference, | |
| 3 | DbCommand = require('../commands/db_command').DbCommand, | |
| 4 | MongoReply = require('../responses/mongo_reply').MongoReply, | |
| 5 | ConnectionPool = require('./connection_pool').ConnectionPool, | |
| 6 | EventEmitter = require('events').EventEmitter, | |
| 7 | ServerCapabilities = require('./server_capabilities').ServerCapabilities, | |
| 8 | Base = require('./base').Base, | |
| 9 | format = require('util').format, | |
| 10 | utils = require('../utils'), | |
| 11 | timers = require('timers'), | |
| 12 | inherits = require('util').inherits; | |
| 13 | ||
| 14 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 15 | 1 | var processor = require('../utils').processor(); |
| 16 | ||
| 17 | /** | |
| 18 | * Class representing a single MongoDB Server connection | |
| 19 | * | |
| 20 | * Options | |
| 21 | * - **readPreference** {String, default:null}, set's the read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST) | |
| 22 | * - **ssl** {Boolean, default:false}, use ssl connection (needs to have a mongod server with ssl support) | |
| 23 | * - **sslValidate** {Boolean, default:false}, validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 24 | * - **sslCA** {Array, default:null}, Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 25 | * - **sslCert** {Buffer/String, default:null}, String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 26 | * - **sslKey** {Buffer/String, default:null}, String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 27 | * - **sslPass** {Buffer/String, default:null}, String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher) | |
| 28 | * - **poolSize** {Number, default:5}, number of connections in the connection pool, set to 5 as default for legacy reasons. | |
| 29 | * - **socketOptions** {Object, default:null}, an object containing socket options to use (noDelay:(boolean), keepAlive:(number), connectTimeoutMS:(number), socketTimeoutMS:(number)) | |
| 30 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 31 | * - **auto_reconnect** {Boolean, default:false}, reconnect on error. | |
| 32 | * - **disableDriverBSONSizeCheck** {Boolean, default:false}, force the server to error if the BSON message is to big | |
| 33 | * | |
| 34 | * @class Represents a Server connection. | |
| 35 | * @param {String} host the server host | |
| 36 | * @param {Number} port the server port | |
| 37 | * @param {Object} [options] optional options for insert command | |
| 38 | */ | |
| 39 | 1 | function Server(host, port, options) { |
| 40 | // Set up Server instance | |
| 41 | 1 | if(!(this instanceof Server)) return new Server(host, port, options); |
| 42 | ||
| 43 | // Set up event emitter | |
| 44 | 1 | Base.call(this); |
| 45 | ||
| 46 | // Ensure correct values | |
| 47 | 1 | if(port != null && typeof port == 'object') { |
| 48 | 0 | options = port; |
| 49 | 0 | port = Connection.DEFAULT_PORT; |
| 50 | } | |
| 51 | ||
| 52 | 1 | var self = this; |
| 53 | 1 | this.host = host; |
| 54 | 1 | this.port = port; |
| 55 | 1 | this.options = options == null ? {} : options; |
| 56 | 1 | this.internalConnection; |
| 57 | 1 | this.internalMaster = false; |
| 58 | 1 | this.connected = false; |
| 59 | 1 | this.poolSize = this.options.poolSize == null ? 5 : this.options.poolSize; |
| 60 | 1 | this.disableDriverBSONSizeCheck = this.options.disableDriverBSONSizeCheck != null ? this.options.disableDriverBSONSizeCheck : false; |
| 61 | 1 | this._used = false; |
| 62 | 1 | this.replicasetInstance = null; |
| 63 | ||
| 64 | // Emit open setup | |
| 65 | 1 | this.emitOpen = this.options.emitOpen || true; |
| 66 | // Set ssl as connection method | |
| 67 | 1 | this.ssl = this.options.ssl == null ? false : this.options.ssl; |
| 68 | // Set ssl validation | |
| 69 | 1 | this.sslValidate = this.options.sslValidate == null ? false : this.options.sslValidate; |
| 70 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 71 | 1 | this.sslCA = Array.isArray(this.options.sslCA) ? this.options.sslCA : null; |
| 72 | // Certificate to present to the server | |
| 73 | 1 | this.sslCert = this.options.sslCert; |
| 74 | // Certificate private key if in separate file | |
| 75 | 1 | this.sslKey = this.options.sslKey; |
| 76 | // Password to unlock private key | |
| 77 | 1 | this.sslPass = this.options.sslPass; |
| 78 | // Server capabilities | |
| 79 | 1 | this.serverCapabilities = null; |
| 80 | // Set server name | |
| 81 | 1 | this.name = format("%s:%s", host, port); |
| 82 | ||
| 83 | // Ensure we are not trying to validate with no list of certificates | |
| 84 | 1 | if(this.sslValidate && (!Array.isArray(this.sslCA) || this.sslCA.length == 0)) { |
| 85 | 0 | throw new Error("The driver expects an Array of CA certificates in the sslCA parameter when enabling sslValidate"); |
| 86 | } | |
| 87 | ||
| 88 | // Get the readPreference | |
| 89 | 1 | var readPreference = this.options['readPreference']; |
| 90 | // If readPreference is an object get the mode string | |
| 91 | 1 | var validateReadPreference = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; |
| 92 | // Read preference setting | |
| 93 | 1 | if(validateReadPreference != null) { |
| 94 | 0 | if(validateReadPreference != ReadPreference.PRIMARY && validateReadPreference != ReadPreference.SECONDARY && validateReadPreference != ReadPreference.NEAREST |
| 95 | && validateReadPreference != ReadPreference.SECONDARY_PREFERRED && validateReadPreference != ReadPreference.PRIMARY_PREFERRED) { | |
| 96 | 0 | throw new Error("Illegal readPreference mode specified, " + validateReadPreference); |
| 97 | } | |
| 98 | ||
| 99 | // Set read Preference | |
| 100 | 0 | this._readPreference = readPreference; |
| 101 | } else { | |
| 102 | 1 | this._readPreference = null; |
| 103 | } | |
| 104 | ||
| 105 | // Contains the isMaster information returned from the server | |
| 106 | 1 | this.isMasterDoc; |
| 107 | ||
| 108 | // Set default connection pool options | |
| 109 | 1 | this.socketOptions = this.options.socketOptions != null ? this.options.socketOptions : {}; |
| 110 | 1 | if(this.disableDriverBSONSizeCheck) this.socketOptions.disableDriverBSONSizeCheck = this.disableDriverBSONSizeCheck; |
| 111 | ||
| 112 | // Set ssl up if it's defined | |
| 113 | 1 | if(this.ssl) { |
| 114 | 0 | this.socketOptions.ssl = true; |
| 115 | // Set ssl validation | |
| 116 | 0 | this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate; |
| 117 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 118 | 0 | this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null; |
| 119 | // Set certificate to present | |
| 120 | 0 | this.socketOptions.sslCert = this.sslCert; |
| 121 | // Set certificate to present | |
| 122 | 0 | this.socketOptions.sslKey = this.sslKey; |
| 123 | // Password to unlock private key | |
| 124 | 0 | this.socketOptions.sslPass = this.sslPass; |
| 125 | } | |
| 126 | ||
| 127 | // Set up logger if any set | |
| 128 | 1 | this.logger = this.options.logger != null |
| 129 | && (typeof this.options.logger.debug == 'function') | |
| 130 | && (typeof this.options.logger.error == 'function') | |
| 131 | && (typeof this.options.logger.log == 'function') | |
| 132 | ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 133 | ||
| 134 | // Just keeps list of events we allow | |
| 135 | 1 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[], timeout:[]}; |
| 136 | // Internal state of server connection | |
| 137 | 1 | this._serverState = 'disconnected'; |
| 138 | // Contains state information about server connection | |
| 139 | 1 | this._state = {'runtimeStats': {'queryStats':new RunningStats()}}; |
| 140 | // Do we record server stats or not | |
| 141 | 1 | this.recordQueryStats = false; |
| 142 | ||
| 143 | // Allow setting the socketTimeoutMS on all connections | |
| 144 | // to work around issues such as secondaries blocking due to compaction | |
| 145 | 1 | utils.setSocketTimeoutProperty(this, this.socketOptions); |
| 146 | }; | |
| 147 | ||
| 148 | /** | |
| 149 | * @ignore | |
| 150 | */ | |
| 151 | 1 | inherits(Server, Base); |
| 152 | ||
| 153 | // | |
| 154 | // Deprecated, USE ReadPreferences class | |
| 155 | // | |
| 156 | 1 | Server.READ_PRIMARY = ReadPreference.PRIMARY; |
| 157 | 1 | Server.READ_SECONDARY = ReadPreference.SECONDARY_PREFERRED; |
| 158 | 1 | Server.READ_SECONDARY_ONLY = ReadPreference.SECONDARY; |
| 159 | ||
| 160 | /** | |
| 161 | * Always ourselves | |
| 162 | * @ignore | |
| 163 | */ | |
| 164 | 1 | Server.prototype.setReadPreference = function(readPreference) { |
| 165 | 0 | this._readPreference = readPreference; |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * @ignore | |
| 170 | */ | |
| 171 | 1 | Server.prototype.isMongos = function() { |
| 172 | 0 | return this.isMasterDoc != null && this.isMasterDoc['msg'] == "isdbgrid" ? true : false; |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * @ignore | |
| 177 | */ | |
| 178 | 1 | Server.prototype._isUsed = function() { |
| 179 | 1 | return this._used; |
| 180 | } | |
| 181 | ||
| 182 | /** | |
| 183 | * @ignore | |
| 184 | */ | |
| 185 | 1 | Server.prototype.close = function(callback) { |
| 186 | // Set server status as disconnected | |
| 187 | 0 | this._serverState = 'destroyed'; |
| 188 | // Remove all local listeners | |
| 189 | 0 | this.removeAllListeners(); |
| 190 | ||
| 191 | 0 | if(this.connectionPool != null) { |
| 192 | // Remove all the listeners on the pool so it does not fire messages all over the place | |
| 193 | 0 | this.connectionPool.removeAllEventListeners(); |
| 194 | // Close the connection if it's open | |
| 195 | 0 | this.connectionPool.stop(true); |
| 196 | } | |
| 197 | ||
| 198 | // Emit close event | |
| 199 | 0 | if(this.db && !this.isSetMember()) { |
| 200 | 0 | var self = this; |
| 201 | 0 | processor(function() { |
| 202 | 0 | self._emitAcrossAllDbInstances(self, null, "close", null, null, true) |
| 203 | }) | |
| 204 | ||
| 205 | // Flush out any remaining call handlers | |
| 206 | 0 | self._flushAllCallHandlers(utils.toError("Connection Closed By Application")); |
| 207 | } | |
| 208 | ||
| 209 | // Peform callback if present | |
| 210 | 0 | if(typeof callback === 'function') callback(null); |
| 211 | }; | |
| 212 | ||
| 213 | 1 | Server.prototype.isDestroyed = function() { |
| 214 | 0 | return this._serverState == 'destroyed'; |
| 215 | } | |
| 216 | ||
| 217 | /** | |
| 218 | * @ignore | |
| 219 | */ | |
| 220 | 1 | Server.prototype.isConnected = function() { |
| 221 | 0 | return this.connectionPool != null && this.connectionPool.isConnected(); |
| 222 | } | |
| 223 | ||
| 224 | /** | |
| 225 | * @ignore | |
| 226 | */ | |
| 227 | 1 | Server.prototype.canWrite = Server.prototype.isConnected; |
| 228 | 1 | Server.prototype.canRead = Server.prototype.isConnected; |
| 229 | ||
| 230 | 1 | Server.prototype.isAutoReconnect = function() { |
| 231 | 0 | if(this.isSetMember()) return false; |
| 232 | 0 | return this.options.auto_reconnect != null ? this.options.auto_reconnect : true; |
| 233 | } | |
| 234 | ||
| 235 | /** | |
| 236 | * @ignore | |
| 237 | */ | |
| 238 | 1 | Server.prototype.allServerInstances = function() { |
| 239 | 0 | return [this]; |
| 240 | } | |
| 241 | ||
| 242 | /** | |
| 243 | * @ignore | |
| 244 | */ | |
| 245 | 1 | Server.prototype.isSetMember = function() { |
| 246 | 0 | return this.replicasetInstance != null || this.mongosInstance != null; |
| 247 | } | |
| 248 | ||
| 249 | /** | |
| 250 | * Assigns a replica set to this `server`. | |
| 251 | * | |
| 252 | * @param {ReplSet} replset | |
| 253 | * @ignore | |
| 254 | */ | |
| 255 | 1 | Server.prototype.assignReplicaSet = function (replset) { |
| 256 | 0 | this.replicasetInstance = replset; |
| 257 | 0 | this.inheritReplSetOptionsFrom(replset); |
| 258 | 0 | this.enableRecordQueryStats(replset.recordQueryStats); |
| 259 | } | |
| 260 | ||
| 261 | /** | |
| 262 | * Takes needed options from `replset` and overwrites | |
| 263 | * our own options. | |
| 264 | * | |
| 265 | * @param {ReplSet} replset | |
| 266 | * @ignore | |
| 267 | */ | |
| 268 | 1 | Server.prototype.inheritReplSetOptionsFrom = function (replset) { |
| 269 | 0 | this.socketOptions = {}; |
| 270 | 0 | this.socketOptions.connectTimeoutMS = replset.options.socketOptions.connectTimeoutMS || 30000; |
| 271 | ||
| 272 | 0 | if(replset.options.ssl) { |
| 273 | // Set ssl on | |
| 274 | 0 | this.socketOptions.ssl = true; |
| 275 | // Set ssl validation | |
| 276 | 0 | this.socketOptions.sslValidate = replset.options.sslValidate == null ? false : replset.options.sslValidate; |
| 277 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 278 | 0 | this.socketOptions.sslCA = Array.isArray(replset.options.sslCA) ? replset.options.sslCA : null; |
| 279 | // Set certificate to present | |
| 280 | 0 | this.socketOptions.sslCert = replset.options.sslCert; |
| 281 | // Set certificate to present | |
| 282 | 0 | this.socketOptions.sslKey = replset.options.sslKey; |
| 283 | // Password to unlock private key | |
| 284 | 0 | this.socketOptions.sslPass = replset.options.sslPass; |
| 285 | } | |
| 286 | ||
| 287 | // If a socket option object exists clone it | |
| 288 | 0 | if(utils.isObject(replset.options.socketOptions)) { |
| 289 | 0 | var keys = Object.keys(replset.options.socketOptions); |
| 290 | 0 | for(var i = 0; i < keys.length; i++) |
| 291 | 0 | this.socketOptions[keys[i]] = replset.options.socketOptions[keys[i]]; |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | /** | |
| 296 | * Opens this server connection. | |
| 297 | * | |
| 298 | * @ignore | |
| 299 | */ | |
| 300 | 1 | Server.prototype.connect = function(dbInstance, options, callback) { |
| 301 | 1 | if('function' === typeof options) callback = options, options = {}; |
| 302 | 1 | if(options == null) options = {}; |
| 303 | 1 | if(!('function' === typeof callback)) callback = null; |
| 304 | 1 | var self = this; |
| 305 | // Save the options | |
| 306 | 1 | this.options = options; |
| 307 | ||
| 308 | // Currently needed to work around problems with multiple connections in a pool with ssl | |
| 309 | // TODO fix if possible | |
| 310 | 1 | if(this.ssl == true) { |
| 311 | // Set up socket options for ssl | |
| 312 | 0 | this.socketOptions.ssl = true; |
| 313 | // Set ssl validation | |
| 314 | 0 | this.socketOptions.sslValidate = this.sslValidate == null ? false : this.sslValidate; |
| 315 | // Set the ssl certificate authority (array of Buffer/String keys) | |
| 316 | 0 | this.socketOptions.sslCA = Array.isArray(this.sslCA) ? this.sslCA : null; |
| 317 | // Set certificate to present | |
| 318 | 0 | this.socketOptions.sslCert = this.sslCert; |
| 319 | // Set certificate to present | |
| 320 | 0 | this.socketOptions.sslKey = this.sslKey; |
| 321 | // Password to unlock private key | |
| 322 | 0 | this.socketOptions.sslPass = this.sslPass; |
| 323 | } | |
| 324 | ||
| 325 | // Let's connect | |
| 326 | 1 | var server = this; |
| 327 | // Let's us override the main receiver of events | |
| 328 | 1 | var eventReceiver = options.eventReceiver != null ? options.eventReceiver : this; |
| 329 | // Save reference to dbInstance | |
| 330 | 1 | this.db = dbInstance; // `db` property matches ReplSet and Mongos |
| 331 | 1 | this.dbInstances = [dbInstance]; |
| 332 | ||
| 333 | // Force connection pool if there is one | |
| 334 | 1 | if(server.connectionPool) server.connectionPool.stop(); |
| 335 | // Set server state to connecting | |
| 336 | 1 | this._serverState = 'connecting'; |
| 337 | ||
| 338 | 1 | if(server.connectionPool != null) { |
| 339 | // Remove all the listeners on the pool so it does not fire messages all over the place | |
| 340 | 0 | this.connectionPool.removeAllEventListeners(); |
| 341 | // Close the connection if it's open | |
| 342 | 0 | this.connectionPool.stop(true); |
| 343 | } | |
| 344 | ||
| 345 | 1 | this.connectionPool = new ConnectionPool(this.host, this.port, this.poolSize, dbInstance.bson, this.socketOptions); |
| 346 | 1 | var connectionPool = this.connectionPool; |
| 347 | // If ssl is not enabled don't wait between the pool connections | |
| 348 | 2 | if(this.ssl == null || !this.ssl) connectionPool._timeToWait = null; |
| 349 | // Set logger on pool | |
| 350 | 1 | connectionPool.logger = this.logger; |
| 351 | 1 | connectionPool.bson = dbInstance.bson; |
| 352 | ||
| 353 | // Set basic parameters passed in | |
| 354 | 1 | var returnIsMasterResults = options.returnIsMasterResults == null ? false : options.returnIsMasterResults; |
| 355 | ||
| 356 | // Create a default connect handler, overriden when using replicasets | |
| 357 | 1 | var connectCallback = function(_server) { |
| 358 | 1 | return function(err, reply) { |
| 359 | // ensure no callbacks get called twice | |
| 360 | 0 | var internalCallback = callback; |
| 361 | 0 | callback = null; |
| 362 | ||
| 363 | // Assign the server | |
| 364 | 0 | _server = _server != null ? _server : server; |
| 365 | ||
| 366 | // If something close down the connection and removed the callback before | |
| 367 | // proxy killed connection etc, ignore the erorr as close event was isssued | |
| 368 | 0 | if(err != null && internalCallback == null) return; |
| 369 | // Internal callback | |
| 370 | 0 | if(err != null) return internalCallback(err, null, _server); |
| 371 | 0 | _server.master = reply.documents[0].ismaster == 1 ? true : false; |
| 372 | 0 | _server.connectionPool.setMaxBsonSize(reply.documents[0].maxBsonObjectSize); |
| 373 | 0 | _server.connectionPool.setMaxMessageSizeBytes(reply.documents[0].maxMessageSizeBytes); |
| 374 | // Set server state to connEcted | |
| 375 | 0 | _server._serverState = 'connected'; |
| 376 | // Set server as connected | |
| 377 | 0 | _server.connected = true; |
| 378 | // Save document returned so we can query it | |
| 379 | 0 | _server.isMasterDoc = reply.documents[0]; |
| 380 | ||
| 381 | 0 | if(self.emitOpen) { |
| 382 | 0 | _server._emitAcrossAllDbInstances(_server, eventReceiver, "open", null, returnIsMasterResults ? reply : null, null); |
| 383 | 0 | self.emitOpen = false; |
| 384 | } else { | |
| 385 | 0 | _server._emitAcrossAllDbInstances(_server, eventReceiver, "reconnect", null, returnIsMasterResults ? reply : null, null); |
| 386 | } | |
| 387 | ||
| 388 | // Set server capabilities | |
| 389 | 0 | server.serverCapabilities = new ServerCapabilities(_server.isMasterDoc); |
| 390 | ||
| 391 | // If we have it set to returnIsMasterResults | |
| 392 | 0 | if(returnIsMasterResults) { |
| 393 | 0 | internalCallback(null, reply, _server); |
| 394 | } else { | |
| 395 | 0 | internalCallback(null, dbInstance, _server); |
| 396 | } | |
| 397 | } | |
| 398 | }; | |
| 399 | ||
| 400 | // Let's us override the main connect callback | |
| 401 | 1 | var connectHandler = options.connectHandler == null ? connectCallback(server) : options.connectHandler; |
| 402 | ||
| 403 | // Set up on connect method | |
| 404 | 1 | connectionPool.on("poolReady", function() { |
| 405 | // Create db command and Add the callback to the list of callbacks by the request id (mapping outgoing messages to correct callbacks) | |
| 406 | 0 | var db_command = DbCommand.NcreateIsMasterCommand(dbInstance, dbInstance.databaseName); |
| 407 | // Check out a reader from the pool | |
| 408 | 0 | var connection = connectionPool.checkoutConnection(); |
| 409 | // Register handler for messages | |
| 410 | 0 | server._registerHandler(db_command, false, connection, connectHandler); |
| 411 | // Write the command out | |
| 412 | 0 | connection.write(db_command); |
| 413 | }) | |
| 414 | ||
| 415 | // Set up item connection | |
| 416 | 1 | connectionPool.on("message", function(message) { |
| 417 | // Attempt to parse the message | |
| 418 | 0 | try { |
| 419 | // Create a new mongo reply | |
| 420 | 0 | var mongoReply = new MongoReply() |
| 421 | // Parse the header | |
| 422 | 0 | mongoReply.parseHeader(message, connectionPool.bson) |
| 423 | ||
| 424 | // If message size is not the same as the buffer size | |
| 425 | // something went terribly wrong somewhere | |
| 426 | 0 | if(mongoReply.messageLength != message.length) { |
| 427 | // Emit the error | |
| 428 | 0 | if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", new Error("bson length is different from message length"), server); |
| 429 | // Remove all listeners | |
| 430 | 0 | server.removeAllListeners(); |
| 431 | } else { | |
| 432 | 0 | var startDate = new Date().getTime(); |
| 433 | ||
| 434 | // Callback instance | |
| 435 | 0 | var callbackInfo = server._findHandler(mongoReply.responseTo.toString()); |
| 436 | ||
| 437 | // The command executed another request, log the handler again under that request id | |
| 438 | 0 | if(mongoReply.requestId > 0 && mongoReply.cursorId.toString() != "0" |
| 439 | && callbackInfo && callbackInfo.info && callbackInfo.info.exhaust) { | |
| 440 | 0 | server._reRegisterHandler(mongoReply.requestId, callbackInfo); |
| 441 | } | |
| 442 | // Parse the body | |
| 443 | 0 | mongoReply.parseBody(message, connectionPool.bson, callbackInfo.info.raw, function(err) { |
| 444 | 0 | if(err != null) { |
| 445 | // If pool connection is already closed | |
| 446 | 0 | if(server._serverState === 'disconnected') return; |
| 447 | // Set server state to disconnected | |
| 448 | 0 | server._serverState = 'disconnected'; |
| 449 | // Remove all listeners and close the connection pool | |
| 450 | 0 | server.removeAllListeners(); |
| 451 | 0 | connectionPool.stop(true); |
| 452 | ||
| 453 | // If we have a callback return the error | |
| 454 | 0 | if(typeof callback === 'function') { |
| 455 | // ensure no callbacks get called twice | |
| 456 | 0 | var internalCallback = callback; |
| 457 | 0 | callback = null; |
| 458 | // Perform callback | |
| 459 | 0 | internalCallback(err, null, server); |
| 460 | 0 | } else if(server.isSetMember()) { |
| 461 | 0 | if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", utils.toError(err), server); |
| 462 | } else { | |
| 463 | 0 | if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", utils.toError(err), server); |
| 464 | } | |
| 465 | ||
| 466 | // If we are a single server connection fire errors correctly | |
| 467 | 0 | if(!server.isSetMember()) { |
| 468 | // Fire all callback errors | |
| 469 | 0 | server.__executeAllCallbacksWithError(err); |
| 470 | // Emit error | |
| 471 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); |
| 472 | } | |
| 473 | // Short cut | |
| 474 | 0 | return; |
| 475 | } | |
| 476 | ||
| 477 | // Let's record the stats info if it's enabled | |
| 478 | 0 | if(server.recordQueryStats == true && server._state['runtimeStats'] != null |
| 479 | && server._state.runtimeStats['queryStats'] instanceof RunningStats) { | |
| 480 | // Add data point to the running statistics object | |
| 481 | 0 | server._state.runtimeStats.queryStats.push(new Date().getTime() - callbackInfo.info.start); |
| 482 | } | |
| 483 | ||
| 484 | // Dispatch the call | |
| 485 | 0 | server._callHandler(mongoReply.responseTo, mongoReply, null); |
| 486 | ||
| 487 | // If we have an error about the server not being master or primary | |
| 488 | 0 | if((mongoReply.responseFlag & (1 << 1)) != 0 |
| 489 | && mongoReply.documents[0].code | |
| 490 | && mongoReply.documents[0].code == 13436) { | |
| 491 | 0 | server.close(); |
| 492 | } | |
| 493 | }); | |
| 494 | } | |
| 495 | } catch (err) { | |
| 496 | // Throw error in next tick | |
| 497 | 0 | processor(function() { |
| 498 | 0 | throw err; |
| 499 | }) | |
| 500 | } | |
| 501 | }); | |
| 502 | ||
| 503 | // Handle timeout | |
| 504 | 1 | connectionPool.on("timeout", function(err) { |
| 505 | // If pool connection is already closed | |
| 506 | 0 | if(server._serverState === 'disconnected' |
| 507 | 0 | || server._serverState === 'destroyed') return; |
| 508 | // Set server state to disconnected | |
| 509 | 0 | server._serverState = 'disconnected'; |
| 510 | // If we have a callback return the error | |
| 511 | 0 | if(typeof callback === 'function') { |
| 512 | // ensure no callbacks get called twice | |
| 513 | 0 | var internalCallback = callback; |
| 514 | 0 | callback = null; |
| 515 | // Perform callback | |
| 516 | 0 | internalCallback(err, null, server); |
| 517 | 0 | } else if(server.isSetMember()) { |
| 518 | 0 | if(server.listeners("timeout") && server.listeners("timeout").length > 0) server.emit("timeout", err, server); |
| 519 | } else { | |
| 520 | 0 | if(eventReceiver.listeners("timeout") && eventReceiver.listeners("timeout").length > 0) eventReceiver.emit("timeout", err, server); |
| 521 | } | |
| 522 | ||
| 523 | // If we are a single server connection fire errors correctly | |
| 524 | 0 | if(!server.isSetMember()) { |
| 525 | // Fire all callback errors | |
| 526 | 0 | server.__executeAllCallbacksWithError(err); |
| 527 | // Emit error | |
| 528 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "timeout", err, server, true); |
| 529 | } | |
| 530 | ||
| 531 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 532 | 0 | if(server.isAutoReconnect() |
| 533 | && !server.isSetMember() | |
| 534 | && (server._serverState != 'destroyed') | |
| 535 | && !server._reconnectInProgreess) { | |
| 536 | // Set the number of retries | |
| 537 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 538 | // Attempt reconnect | |
| 539 | 0 | server._reconnectInProgreess = true; |
| 540 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 541 | } | |
| 542 | }); | |
| 543 | ||
| 544 | // Handle errors | |
| 545 | 1 | connectionPool.on("error", function(message, connection, error_options) { |
| 546 | // If pool connection is already closed | |
| 547 | 0 | if(server._serverState === 'disconnected' |
| 548 | 0 | || server._serverState === 'destroyed') return; |
| 549 | ||
| 550 | // Set server state to disconnected | |
| 551 | 0 | server._serverState = 'disconnected'; |
| 552 | // Error message | |
| 553 | 0 | var error_message = new Error(message && message.err ? message.err : message); |
| 554 | // Error message coming from ssl | |
| 555 | 0 | if(error_options && error_options.ssl) error_message.ssl = true; |
| 556 | ||
| 557 | // If we have a callback return the error | |
| 558 | 0 | if(typeof callback === 'function') { |
| 559 | // ensure no callbacks get called twice | |
| 560 | 0 | var internalCallback = callback; |
| 561 | 0 | callback = null; |
| 562 | // Perform callback | |
| 563 | 0 | internalCallback(error_message, null, server); |
| 564 | 0 | } else if(server.isSetMember()) { |
| 565 | 0 | if(server.listeners("error") && server.listeners("error").length > 0) server.emit("error", error_message, server); |
| 566 | } else { | |
| 567 | 0 | if(eventReceiver.listeners("error") && eventReceiver.listeners("error").length > 0) eventReceiver.emit("error", error_message, server); |
| 568 | } | |
| 569 | ||
| 570 | // If we are a single server connection fire errors correctly | |
| 571 | 0 | if(!server.isSetMember()) { |
| 572 | // Fire all callback errors | |
| 573 | 0 | server.__executeAllCallbacksWithError(error_message); |
| 574 | // Emit error | |
| 575 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "error", error_message, server, true); |
| 576 | } | |
| 577 | ||
| 578 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 579 | 0 | if(server.isAutoReconnect() |
| 580 | && !server.isSetMember() | |
| 581 | && (server._serverState != 'destroyed') | |
| 582 | && !server._reconnectInProgreess) { | |
| 583 | ||
| 584 | // Set the number of retries | |
| 585 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 586 | // Attempt reconnect | |
| 587 | 0 | server._reconnectInProgreess = true; |
| 588 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 589 | } | |
| 590 | }); | |
| 591 | ||
| 592 | // Handle close events | |
| 593 | 1 | connectionPool.on("close", function() { |
| 594 | // If pool connection is already closed | |
| 595 | 0 | if(server._serverState === 'disconnected' |
| 596 | 0 | || server._serverState === 'destroyed') return; |
| 597 | // Set server state to disconnected | |
| 598 | 0 | server._serverState = 'disconnected'; |
| 599 | // If we have a callback return the error | |
| 600 | 0 | if(typeof callback == 'function') { |
| 601 | // ensure no callbacks get called twice | |
| 602 | 0 | var internalCallback = callback; |
| 603 | 0 | callback = null; |
| 604 | // Perform callback | |
| 605 | 0 | internalCallback(new Error("connection closed"), null, server); |
| 606 | 0 | } else if(server.isSetMember()) { |
| 607 | 0 | if(server.listeners("close") && server.listeners("close").length > 0) server.emit("close", new Error("connection closed"), server); |
| 608 | } else { | |
| 609 | 0 | if(eventReceiver.listeners("close") && eventReceiver.listeners("close").length > 0) eventReceiver.emit("close", new Error("connection closed"), server); |
| 610 | } | |
| 611 | ||
| 612 | // If we are a single server connection fire errors correctly | |
| 613 | 0 | if(!server.isSetMember()) { |
| 614 | // Fire all callback errors | |
| 615 | 0 | server.__executeAllCallbacksWithError(new Error("connection closed")); |
| 616 | // Emit error | |
| 617 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "close", server, null, true); |
| 618 | } | |
| 619 | ||
| 620 | // If we have autoConnect enabled let's fire up an attempt to reconnect | |
| 621 | 0 | if(server.isAutoReconnect() |
| 622 | && !server.isSetMember() | |
| 623 | && (server._serverState != 'destroyed') | |
| 624 | && !server._reconnectInProgreess) { | |
| 625 | ||
| 626 | // Set the number of retries | |
| 627 | 0 | server._reconnect_retries = server.db.numberOfRetries; |
| 628 | // Attempt reconnect | |
| 629 | 0 | server._reconnectInProgreess = true; |
| 630 | 0 | setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 631 | } | |
| 632 | }); | |
| 633 | ||
| 634 | /** | |
| 635 | * @ignore | |
| 636 | */ | |
| 637 | 1 | var __attemptReconnect = function(server) { |
| 638 | 0 | return function() { |
| 639 | // Attempt reconnect | |
| 640 | 0 | server.connect(server.db, server.options, function(err, result) { |
| 641 | 0 | server._reconnect_retries = server._reconnect_retries - 1; |
| 642 | ||
| 643 | 0 | if(err) { |
| 644 | // Retry | |
| 645 | 0 | if(server._reconnect_retries == 0 || server._serverState == 'destroyed') { |
| 646 | 0 | server._serverState = 'connected'; |
| 647 | 0 | server._reconnectInProgreess = false |
| 648 | // Fire all callback errors | |
| 649 | 0 | return server.__executeAllCallbacksWithError(new Error("failed to reconnect to server")); |
| 650 | } else { | |
| 651 | 0 | return setTimeout(__attemptReconnect(server), server.db.retryMiliSeconds); |
| 652 | } | |
| 653 | } else { | |
| 654 | // Set as authenticating (isConnected will be false) | |
| 655 | 0 | server._serverState = 'authenticating'; |
| 656 | // Apply any auths, we don't try to catch any errors here | |
| 657 | // as there are nowhere to simply propagate them to | |
| 658 | 0 | self._apply_auths(server.db, function(err, result) { |
| 659 | 0 | server._serverState = 'connected'; |
| 660 | 0 | server._reconnectInProgreess = false; |
| 661 | 0 | server._commandsStore.execute_queries(); |
| 662 | 0 | server._commandsStore.execute_writes(); |
| 663 | }); | |
| 664 | } | |
| 665 | }); | |
| 666 | } | |
| 667 | } | |
| 668 | ||
| 669 | // If we have a parser error we are in an unknown state, close everything and emit | |
| 670 | // error | |
| 671 | 1 | connectionPool.on("parseError", function(err) { |
| 672 | // If pool connection is already closed | |
| 673 | 0 | if(server._serverState === 'disconnected' |
| 674 | 0 | || server._serverState === 'destroyed') return; |
| 675 | // Set server state to disconnected | |
| 676 | 0 | server._serverState = 'disconnected'; |
| 677 | // If we have a callback return the error | |
| 678 | 0 | if(typeof callback === 'function') { |
| 679 | // ensure no callbacks get called twice | |
| 680 | 0 | var internalCallback = callback; |
| 681 | 0 | callback = null; |
| 682 | // Perform callback | |
| 683 | 0 | internalCallback(utils.toError(err), null, server); |
| 684 | 0 | } else if(server.isSetMember()) { |
| 685 | 0 | if(server.listeners("parseError") && server.listeners("parseError").length > 0) server.emit("parseError", utils.toError(err), server); |
| 686 | } else { | |
| 687 | 0 | if(eventReceiver.listeners("parseError") && eventReceiver.listeners("parseError").length > 0) eventReceiver.emit("parseError", utils.toError(err), server); |
| 688 | } | |
| 689 | ||
| 690 | // If we are a single server connection fire errors correctly | |
| 691 | 0 | if(!server.isSetMember()) { |
| 692 | // Fire all callback errors | |
| 693 | 0 | server.__executeAllCallbacksWithError(utils.toError(err)); |
| 694 | // Emit error | |
| 695 | 0 | server._emitAcrossAllDbInstances(server, eventReceiver, "parseError", server, null, true); |
| 696 | } | |
| 697 | }); | |
| 698 | ||
| 699 | // Boot up connection poole, pass in a locator of callbacks | |
| 700 | 1 | connectionPool.start(); |
| 701 | } | |
| 702 | ||
| 703 | /** | |
| 704 | * @ignore | |
| 705 | */ | |
| 706 | 1 | Server.prototype.allRawConnections = function() { |
| 707 | 0 | return this.connectionPool != null ? this.connectionPool.getAllConnections() : []; |
| 708 | } | |
| 709 | ||
| 710 | /** | |
| 711 | * Check if a writer can be provided | |
| 712 | * @ignore | |
| 713 | */ | |
| 714 | 1 | var canCheckoutWriter = function(self, read) { |
| 715 | // We cannot write to an arbiter or secondary server | |
| 716 | 0 | if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) { |
| 717 | 0 | return new Error("Cannot write to an arbiter"); |
| 718 | 0 | } if(self.isMasterDoc && self.isMasterDoc['secondary'] == true) { |
| 719 | 0 | return new Error("Cannot write to a secondary"); |
| 720 | 0 | } else if(read == true && self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) { |
| 721 | 0 | return new Error("Cannot read from primary when secondary only specified"); |
| 722 | 0 | } else if(!self.isMasterDoc) { |
| 723 | 0 | return new Error("Cannot determine state of server"); |
| 724 | } | |
| 725 | ||
| 726 | // Return no error | |
| 727 | 0 | return null; |
| 728 | } | |
| 729 | ||
| 730 | /** | |
| 731 | * @ignore | |
| 732 | */ | |
| 733 | 1 | Server.prototype.checkoutWriter = function(read) { |
| 734 | 0 | if(read == true) return this.connectionPool.checkoutConnection(); |
| 735 | // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) | |
| 736 | 0 | var result = canCheckoutWriter(this, read); |
| 737 | // If the result is null check out a writer | |
| 738 | 0 | if(result == null && this.connectionPool != null) { |
| 739 | 0 | var connection = this.connectionPool.checkoutConnection(); |
| 740 | // Add server capabilities to the connection | |
| 741 | 0 | if(connection) |
| 742 | 0 | connection.serverCapabilities = this.serverCapabilities; |
| 743 | 0 | return connection; |
| 744 | 0 | } else if(result == null) { |
| 745 | 0 | return null; |
| 746 | } else { | |
| 747 | 0 | return result; |
| 748 | } | |
| 749 | } | |
| 750 | ||
| 751 | /** | |
| 752 | * Check if a reader can be provided | |
| 753 | * @ignore | |
| 754 | */ | |
| 755 | 1 | var canCheckoutReader = function(self) { |
| 756 | // We cannot write to an arbiter or secondary server | |
| 757 | 0 | if(self.isMasterDoc && self.isMasterDoc['arbiterOnly'] == true) { |
| 758 | 0 | return new Error("Cannot write to an arbiter"); |
| 759 | 0 | } else if(self._readPreference != null) { |
| 760 | // If the read preference is Primary and the instance is not a master return an error | |
| 761 | 0 | if((self._readPreference == ReadPreference.PRIMARY) && self.isMasterDoc && self.isMasterDoc['ismaster'] != true) { |
| 762 | 0 | return new Error("Read preference is Server.PRIMARY and server is not master"); |
| 763 | 0 | } else if(self._readPreference == ReadPreference.SECONDARY && self.isMasterDoc && self.isMasterDoc['ismaster'] == true) { |
| 764 | 0 | return new Error("Cannot read from primary when secondary only specified"); |
| 765 | } | |
| 766 | 0 | } else if(!self.isMasterDoc) { |
| 767 | 0 | return new Error("Cannot determine state of server"); |
| 768 | } | |
| 769 | ||
| 770 | // Return no error | |
| 771 | 0 | return null; |
| 772 | } | |
| 773 | ||
| 774 | /** | |
| 775 | * @ignore | |
| 776 | */ | |
| 777 | 1 | Server.prototype.checkoutReader = function(read) { |
| 778 | // Check if are allowed to do a checkout (if we try to use an arbiter f.ex) | |
| 779 | 0 | var result = canCheckoutReader(this); |
| 780 | // If the result is null check out a writer | |
| 781 | 0 | if(result == null && this.connectionPool != null) { |
| 782 | 0 | var connection = this.connectionPool.checkoutConnection(); |
| 783 | // Add server capabilities to the connection | |
| 784 | 0 | if(connection) |
| 785 | 0 | connection.serverCapabilities = this.serverCapabilities; |
| 786 | 0 | return connection; |
| 787 | 0 | } else if(result == null) { |
| 788 | 0 | return null; |
| 789 | } else { | |
| 790 | 0 | return result; |
| 791 | } | |
| 792 | } | |
| 793 | ||
| 794 | /** | |
| 795 | * @ignore | |
| 796 | */ | |
| 797 | 1 | Server.prototype.enableRecordQueryStats = function(enable) { |
| 798 | 0 | this.recordQueryStats = enable; |
| 799 | } | |
| 800 | ||
| 801 | /** | |
| 802 | * Internal statistics object used for calculating average and standard devitation on | |
| 803 | * running queries | |
| 804 | * @ignore | |
| 805 | */ | |
| 806 | 1 | var RunningStats = function() { |
| 807 | 1 | var self = this; |
| 808 | 1 | this.m_n = 0; |
| 809 | 1 | this.m_oldM = 0.0; |
| 810 | 1 | this.m_oldS = 0.0; |
| 811 | 1 | this.m_newM = 0.0; |
| 812 | 1 | this.m_newS = 0.0; |
| 813 | ||
| 814 | // Define getters | |
| 815 | 1 | Object.defineProperty(this, "numDataValues", { enumerable: true |
| 816 | 0 | , get: function () { return this.m_n; } |
| 817 | }); | |
| 818 | ||
| 819 | 1 | Object.defineProperty(this, "mean", { enumerable: true |
| 820 | 0 | , get: function () { return (this.m_n > 0) ? this.m_newM : 0.0; } |
| 821 | }); | |
| 822 | ||
| 823 | 1 | Object.defineProperty(this, "variance", { enumerable: true |
| 824 | 0 | , get: function () { return ((this.m_n > 1) ? this.m_newS/(this.m_n - 1) : 0.0); } |
| 825 | }); | |
| 826 | ||
| 827 | 1 | Object.defineProperty(this, "standardDeviation", { enumerable: true |
| 828 | 0 | , get: function () { return Math.sqrt(this.variance); } |
| 829 | }); | |
| 830 | ||
| 831 | 1 | Object.defineProperty(this, "sScore", { enumerable: true |
| 832 | , get: function () { | |
| 833 | 0 | var bottom = this.mean + this.standardDeviation; |
| 834 | 0 | if(bottom == 0) return 0; |
| 835 | 0 | return ((2 * this.mean * this.standardDeviation)/(bottom)); |
| 836 | } | |
| 837 | }); | |
| 838 | } | |
| 839 | ||
| 840 | /** | |
| 841 | * @ignore | |
| 842 | */ | |
| 843 | 1 | RunningStats.prototype.push = function(x) { |
| 844 | // Update the number of samples | |
| 845 | 0 | this.m_n = this.m_n + 1; |
| 846 | // See Knuth TAOCP vol 2, 3rd edition, page 232 | |
| 847 | 0 | if(this.m_n == 1) { |
| 848 | 0 | this.m_oldM = this.m_newM = x; |
| 849 | 0 | this.m_oldS = 0.0; |
| 850 | } else { | |
| 851 | 0 | this.m_newM = this.m_oldM + (x - this.m_oldM) / this.m_n; |
| 852 | 0 | this.m_newS = this.m_oldS + (x - this.m_oldM) * (x - this.m_newM); |
| 853 | ||
| 854 | // set up for next iteration | |
| 855 | 0 | this.m_oldM = this.m_newM; |
| 856 | 0 | this.m_oldS = this.m_newS; |
| 857 | } | |
| 858 | } | |
| 859 | ||
| 860 | /** | |
| 861 | * @ignore | |
| 862 | */ | |
| 863 | 1 | Object.defineProperty(Server.prototype, "autoReconnect", { enumerable: true |
| 864 | , get: function () { | |
| 865 | 0 | return this.options['auto_reconnect'] == null ? false : this.options['auto_reconnect']; |
| 866 | } | |
| 867 | }); | |
| 868 | ||
| 869 | /** | |
| 870 | * @ignore | |
| 871 | */ | |
| 872 | 1 | Object.defineProperty(Server.prototype, "connection", { enumerable: true |
| 873 | , get: function () { | |
| 874 | 0 | return this.internalConnection; |
| 875 | } | |
| 876 | , set: function(connection) { | |
| 877 | 0 | this.internalConnection = connection; |
| 878 | } | |
| 879 | }); | |
| 880 | ||
| 881 | /** | |
| 882 | * @ignore | |
| 883 | */ | |
| 884 | 1 | Object.defineProperty(Server.prototype, "master", { enumerable: true |
| 885 | , get: function () { | |
| 886 | 0 | return this.internalMaster; |
| 887 | } | |
| 888 | , set: function(value) { | |
| 889 | 0 | this.internalMaster = value; |
| 890 | } | |
| 891 | }); | |
| 892 | ||
| 893 | /** | |
| 894 | * @ignore | |
| 895 | */ | |
| 896 | 1 | Object.defineProperty(Server.prototype, "primary", { enumerable: true |
| 897 | , get: function () { | |
| 898 | 0 | return this; |
| 899 | } | |
| 900 | }); | |
| 901 | ||
| 902 | /** | |
| 903 | * Getter for query Stats | |
| 904 | * @ignore | |
| 905 | */ | |
| 906 | 1 | Object.defineProperty(Server.prototype, "queryStats", { enumerable: true |
| 907 | , get: function () { | |
| 908 | 0 | return this._state.runtimeStats.queryStats; |
| 909 | } | |
| 910 | }); | |
| 911 | ||
| 912 | /** | |
| 913 | * @ignore | |
| 914 | */ | |
| 915 | 1 | Object.defineProperty(Server.prototype, "runtimeStats", { enumerable: true |
| 916 | , get: function () { | |
| 917 | 0 | return this._state.runtimeStats; |
| 918 | } | |
| 919 | }); | |
| 920 | ||
| 921 | /** | |
| 922 | * Get Read Preference method | |
| 923 | * @ignore | |
| 924 | */ | |
| 925 | 1 | Object.defineProperty(Server.prototype, "readPreference", { enumerable: true |
| 926 | , get: function () { | |
| 927 | 0 | if(this._readPreference == null && this.readSecondary) { |
| 928 | 0 | return Server.READ_SECONDARY; |
| 929 | 0 | } else if(this._readPreference == null && !this.readSecondary) { |
| 930 | 0 | return Server.READ_PRIMARY; |
| 931 | } else { | |
| 932 | 0 | return this._readPreference; |
| 933 | } | |
| 934 | } | |
| 935 | }); | |
| 936 | ||
| 937 | /** | |
| 938 | * @ignore | |
| 939 | */ | |
| 940 | 1 | exports.Server = Server; |
| 941 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var ServerCapabilities = function(isMasterResult) { |
| 2 | // Capabilities | |
| 3 | 0 | var aggregationCursor = false; |
| 4 | 0 | var writeCommands = false; |
| 5 | 0 | var textSearch = false; |
| 6 | 0 | var authCommands = false; |
| 7 | 0 | var maxNumberOfDocsInBatch = 1000; |
| 8 | ||
| 9 | 0 | if(isMasterResult.minWireVersion >= 0) { |
| 10 | 0 | textSearch = true; |
| 11 | } | |
| 12 | ||
| 13 | 0 | if(isMasterResult.maxWireVersion >= 1) { |
| 14 | 0 | aggregationCursor = true; |
| 15 | 0 | authCommands = true; |
| 16 | } | |
| 17 | ||
| 18 | 0 | if(isMasterResult.maxWireVersion >= 2) { |
| 19 | 0 | writeCommands = true; |
| 20 | } | |
| 21 | ||
| 22 | // If no min or max wire version set to 0 | |
| 23 | 0 | if(isMasterResult.minWireVersion == null) { |
| 24 | 0 | isMasterResult.minWireVersion = 0; |
| 25 | } | |
| 26 | ||
| 27 | 0 | if(isMasterResult.maxWireVersion == null) { |
| 28 | 0 | isMasterResult.maxWireVersion = 0; |
| 29 | } | |
| 30 | ||
| 31 | // Map up read only parameters | |
| 32 | 0 | setup_get_property(this, "hasAggregationCursor", aggregationCursor); |
| 33 | 0 | setup_get_property(this, "hasWriteCommands", writeCommands); |
| 34 | 0 | setup_get_property(this, "hasTextSearch", textSearch); |
| 35 | 0 | setup_get_property(this, "hasAuthCommands", authCommands); |
| 36 | 0 | setup_get_property(this, "minWireVersion", isMasterResult.minWireVersion); |
| 37 | 0 | setup_get_property(this, "maxWireVersion", isMasterResult.maxWireVersion); |
| 38 | 0 | setup_get_property(this, "maxNumberOfDocsInBatch", maxNumberOfDocsInBatch); |
| 39 | } | |
| 40 | ||
| 41 | 1 | var setup_get_property = function(object, name, value) { |
| 42 | 0 | Object.defineProperty(object, name, { |
| 43 | enumerable: true | |
| 44 | 0 | , get: function () { return value; } |
| 45 | }); | |
| 46 | } | |
| 47 | ||
| 48 | 1 | exports.ServerCapabilities = ServerCapabilities; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var fs = require('fs'), |
| 2 | ReadPreference = require('./read_preference').ReadPreference; | |
| 3 | ||
| 4 | 1 | exports.parse = function(url, options) { |
| 5 | // Ensure we have a default options object if none set | |
| 6 | 0 | options = options || {}; |
| 7 | // Variables | |
| 8 | 0 | var connection_part = ''; |
| 9 | 0 | var auth_part = ''; |
| 10 | 0 | var query_string_part = ''; |
| 11 | 0 | var dbName = 'admin'; |
| 12 | ||
| 13 | // Must start with mongodb | |
| 14 | 0 | if(url.indexOf("mongodb://") != 0) |
| 15 | 0 | throw Error("URL must be in the format mongodb://user:pass@host:port/dbname"); |
| 16 | // If we have a ? mark cut the query elements off | |
| 17 | 0 | if(url.indexOf("?") != -1) { |
| 18 | 0 | query_string_part = url.substr(url.indexOf("?") + 1); |
| 19 | 0 | connection_part = url.substring("mongodb://".length, url.indexOf("?")) |
| 20 | } else { | |
| 21 | 0 | connection_part = url.substring("mongodb://".length); |
| 22 | } | |
| 23 | ||
| 24 | // Check if we have auth params | |
| 25 | 0 | if(connection_part.indexOf("@") != -1) { |
| 26 | 0 | auth_part = connection_part.split("@")[0]; |
| 27 | 0 | connection_part = connection_part.split("@")[1]; |
| 28 | } | |
| 29 | ||
| 30 | // Check if the connection string has a db | |
| 31 | 0 | if(connection_part.indexOf(".sock") != -1) { |
| 32 | 0 | if(connection_part.indexOf(".sock/") != -1) { |
| 33 | 0 | dbName = connection_part.split(".sock/")[1]; |
| 34 | 0 | connection_part = connection_part.split("/", connection_part.indexOf(".sock") + ".sock".length); |
| 35 | } | |
| 36 | 0 | } else if(connection_part.indexOf("/") != -1) { |
| 37 | 0 | dbName = connection_part.split("/")[1]; |
| 38 | 0 | connection_part = connection_part.split("/")[0]; |
| 39 | } | |
| 40 | ||
| 41 | // Result object | |
| 42 | 0 | var object = {}; |
| 43 | ||
| 44 | // Pick apart the authentication part of the string | |
| 45 | 0 | var authPart = auth_part || ''; |
| 46 | 0 | var auth = authPart.split(':', 2); |
| 47 | 0 | if(options['uri_decode_auth']){ |
| 48 | 0 | auth[0] = decodeURIComponent(auth[0]); |
| 49 | 0 | if(auth[1]){ |
| 50 | 0 | auth[1] = decodeURIComponent(auth[1]); |
| 51 | } | |
| 52 | } | |
| 53 | ||
| 54 | // Add auth to final object if we have 2 elements | |
| 55 | 0 | if(auth.length == 2) object.auth = {user: auth[0], password: auth[1]}; |
| 56 | ||
| 57 | // Variables used for temporary storage | |
| 58 | 0 | var hostPart; |
| 59 | 0 | var urlOptions; |
| 60 | 0 | var servers; |
| 61 | 0 | var serverOptions = {socketOptions: {}}; |
| 62 | 0 | var dbOptions = {read_preference_tags: []}; |
| 63 | 0 | var replSetServersOptions = {socketOptions: {}}; |
| 64 | // Add server options to final object | |
| 65 | 0 | object.server_options = serverOptions; |
| 66 | 0 | object.db_options = dbOptions; |
| 67 | 0 | object.rs_options = replSetServersOptions; |
| 68 | 0 | object.mongos_options = {}; |
| 69 | ||
| 70 | // Let's check if we are using a domain socket | |
| 71 | 0 | if(url.match(/\.sock/)) { |
| 72 | // Split out the socket part | |
| 73 | 0 | var domainSocket = url.substring( |
| 74 | url.indexOf("mongodb://") + "mongodb://".length | |
| 75 | , url.lastIndexOf(".sock") + ".sock".length); | |
| 76 | // Clean out any auth stuff if any | |
| 77 | 0 | if(domainSocket.indexOf("@") != -1) domainSocket = domainSocket.split("@")[1]; |
| 78 | 0 | servers = [{domain_socket: domainSocket}]; |
| 79 | } else { | |
| 80 | // Split up the db | |
| 81 | 0 | hostPart = connection_part; |
| 82 | // Parse all server results | |
| 83 | 0 | servers = hostPart.split(',').map(function(h) { |
| 84 | 0 | var hostPort = h.split(':', 2); |
| 85 | 0 | var _host = hostPort[0] || 'localhost'; |
| 86 | 0 | var _port = hostPort[1] != null ? parseInt(hostPort[1], 10) : 27017; |
| 87 | // Check for localhost?safe=true style case | |
| 88 | 0 | if(_host.indexOf("?") != -1) _host = _host.split(/\?/)[0]; |
| 89 | ||
| 90 | // Return the mapped object | |
| 91 | 0 | return {host: _host, port: _port}; |
| 92 | }); | |
| 93 | } | |
| 94 | ||
| 95 | // Get the db name | |
| 96 | 0 | object.dbName = dbName || 'admin'; |
| 97 | // Split up all the options | |
| 98 | 0 | urlOptions = (query_string_part || '').split(/[&;]/); |
| 99 | // Ugh, we have to figure out which options go to which constructor manually. | |
| 100 | 0 | urlOptions.forEach(function(opt) { |
| 101 | 0 | if(!opt) return; |
| 102 | 0 | var splitOpt = opt.split('='), name = splitOpt[0], value = splitOpt[1]; |
| 103 | // Options implementations | |
| 104 | 0 | switch(name) { |
| 105 | case 'slaveOk': | |
| 106 | case 'slave_ok': | |
| 107 | 0 | serverOptions.slave_ok = (value == 'true'); |
| 108 | 0 | dbOptions.slaveOk = (value == 'true'); |
| 109 | 0 | break; |
| 110 | case 'maxPoolSize': | |
| 111 | case 'poolSize': | |
| 112 | 0 | serverOptions.poolSize = parseInt(value, 10); |
| 113 | 0 | replSetServersOptions.poolSize = parseInt(value, 10); |
| 114 | 0 | break; |
| 115 | case 'autoReconnect': | |
| 116 | case 'auto_reconnect': | |
| 117 | 0 | serverOptions.auto_reconnect = (value == 'true'); |
| 118 | 0 | break; |
| 119 | case 'minPoolSize': | |
| 120 | 0 | throw new Error("minPoolSize not supported"); |
| 121 | case 'maxIdleTimeMS': | |
| 122 | 0 | throw new Error("maxIdleTimeMS not supported"); |
| 123 | case 'waitQueueMultiple': | |
| 124 | 0 | throw new Error("waitQueueMultiple not supported"); |
| 125 | case 'waitQueueTimeoutMS': | |
| 126 | 0 | throw new Error("waitQueueTimeoutMS not supported"); |
| 127 | case 'uuidRepresentation': | |
| 128 | 0 | throw new Error("uuidRepresentation not supported"); |
| 129 | case 'ssl': | |
| 130 | 0 | if(value == 'prefer') { |
| 131 | 0 | serverOptions.ssl = value; |
| 132 | 0 | replSetServersOptions.ssl = value; |
| 133 | 0 | break; |
| 134 | } | |
| 135 | 0 | serverOptions.ssl = (value == 'true'); |
| 136 | 0 | replSetServersOptions.ssl = (value == 'true'); |
| 137 | 0 | break; |
| 138 | case 'replicaSet': | |
| 139 | case 'rs_name': | |
| 140 | 0 | replSetServersOptions.rs_name = value; |
| 141 | 0 | break; |
| 142 | case 'reconnectWait': | |
| 143 | 0 | replSetServersOptions.reconnectWait = parseInt(value, 10); |
| 144 | 0 | break; |
| 145 | case 'retries': | |
| 146 | 0 | replSetServersOptions.retries = parseInt(value, 10); |
| 147 | 0 | break; |
| 148 | case 'readSecondary': | |
| 149 | case 'read_secondary': | |
| 150 | 0 | replSetServersOptions.read_secondary = (value == 'true'); |
| 151 | 0 | break; |
| 152 | case 'fsync': | |
| 153 | 0 | dbOptions.fsync = (value == 'true'); |
| 154 | 0 | break; |
| 155 | case 'journal': | |
| 156 | 0 | dbOptions.journal = (value == 'true'); |
| 157 | 0 | break; |
| 158 | case 'safe': | |
| 159 | 0 | dbOptions.safe = (value == 'true'); |
| 160 | 0 | break; |
| 161 | case 'nativeParser': | |
| 162 | case 'native_parser': | |
| 163 | 0 | dbOptions.native_parser = (value == 'true'); |
| 164 | 0 | break; |
| 165 | case 'connectTimeoutMS': | |
| 166 | 0 | serverOptions.socketOptions.connectTimeoutMS = parseInt(value, 10); |
| 167 | 0 | replSetServersOptions.socketOptions.connectTimeoutMS = parseInt(value, 10); |
| 168 | 0 | break; |
| 169 | case 'socketTimeoutMS': | |
| 170 | 0 | serverOptions.socketOptions.socketTimeoutMS = parseInt(value, 10); |
| 171 | 0 | replSetServersOptions.socketOptions.socketTimeoutMS = parseInt(value, 10); |
| 172 | 0 | break; |
| 173 | case 'w': | |
| 174 | 0 | dbOptions.w = parseInt(value, 10); |
| 175 | 0 | break; |
| 176 | case 'authSource': | |
| 177 | 0 | dbOptions.authSource = value; |
| 178 | 0 | break; |
| 179 | case 'gssapiServiceName': | |
| 180 | 0 | dbOptions.gssapiServiceName = value; |
| 181 | 0 | break; |
| 182 | case 'authMechanism': | |
| 183 | 0 | if(value == 'GSSAPI') { |
| 184 | // If no password provided decode only the principal | |
| 185 | 0 | if(object.auth == null) { |
| 186 | 0 | var urlDecodeAuthPart = decodeURIComponent(authPart); |
| 187 | 0 | if(urlDecodeAuthPart.indexOf("@") == -1) throw new Error("GSSAPI requires a provided principal"); |
| 188 | 0 | object.auth = {user: urlDecodeAuthPart, password: null}; |
| 189 | } else { | |
| 190 | 0 | object.auth.user = decodeURIComponent(object.auth.user); |
| 191 | } | |
| 192 | 0 | } else if(value == 'MONGODB-X509') { |
| 193 | 0 | object.auth = {user: decodeURIComponent(authPart)}; |
| 194 | } | |
| 195 | ||
| 196 | // Only support GSSAPI or MONGODB-CR for now | |
| 197 | 0 | if(value != 'GSSAPI' |
| 198 | && value != 'MONGODB-X509' | |
| 199 | && value != 'MONGODB-CR' | |
| 200 | && value != 'PLAIN') | |
| 201 | 0 | throw new Error("only GSSAPI, PLAIN, MONGODB-X509 or MONGODB-CR is supported by authMechanism"); |
| 202 | ||
| 203 | // Authentication mechanism | |
| 204 | 0 | dbOptions.authMechanism = value; |
| 205 | 0 | break; |
| 206 | case 'wtimeoutMS': | |
| 207 | 0 | dbOptions.wtimeoutMS = parseInt(value, 10); |
| 208 | 0 | break; |
| 209 | case 'readPreference': | |
| 210 | 0 | if(!ReadPreference.isValid(value)) throw new Error("readPreference must be either primary/primaryPreferred/secondary/secondaryPreferred/nearest"); |
| 211 | 0 | dbOptions.read_preference = value; |
| 212 | 0 | break; |
| 213 | case 'readPreferenceTags': | |
| 214 | // Contains the tag object | |
| 215 | 0 | var tagObject = {}; |
| 216 | 0 | if(value == null || value == '') { |
| 217 | 0 | dbOptions.read_preference_tags.push(tagObject); |
| 218 | 0 | break; |
| 219 | } | |
| 220 | ||
| 221 | // Split up the tags | |
| 222 | 0 | var tags = value.split(/\,/); |
| 223 | 0 | for(var i = 0; i < tags.length; i++) { |
| 224 | 0 | var parts = tags[i].trim().split(/\:/); |
| 225 | 0 | tagObject[parts[0]] = parts[1]; |
| 226 | } | |
| 227 | ||
| 228 | // Set the preferences tags | |
| 229 | 0 | dbOptions.read_preference_tags.push(tagObject); |
| 230 | 0 | break; |
| 231 | default: | |
| 232 | 0 | break; |
| 233 | } | |
| 234 | }); | |
| 235 | ||
| 236 | // No tags: should be null (not []) | |
| 237 | 0 | if(dbOptions.read_preference_tags.length === 0) { |
| 238 | 0 | dbOptions.read_preference_tags = null; |
| 239 | } | |
| 240 | ||
| 241 | // Validate if there are an invalid write concern combinations | |
| 242 | 0 | if((dbOptions.w == -1 || dbOptions.w == 0) && ( |
| 243 | dbOptions.journal == true | |
| 244 | || dbOptions.fsync == true | |
| 245 | 0 | || dbOptions.safe == true)) throw new Error("w set to -1 or 0 cannot be combined with safe/w/journal/fsync") |
| 246 | ||
| 247 | // If no read preference set it to primary | |
| 248 | 0 | if(!dbOptions.read_preference) dbOptions.read_preference = 'primary'; |
| 249 | ||
| 250 | // Add servers to result | |
| 251 | 0 | object.servers = servers; |
| 252 | // Returned parsed object | |
| 253 | 0 | return object; |
| 254 | } | |
| 255 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var QueryCommand = require('./commands/query_command').QueryCommand, |
| 2 | GetMoreCommand = require('./commands/get_more_command').GetMoreCommand, | |
| 3 | KillCursorCommand = require('./commands/kill_cursor_command').KillCursorCommand, | |
| 4 | Long = require('bson').Long, | |
| 5 | ReadPreference = require('./connection/read_preference').ReadPreference, | |
| 6 | CursorStream = require('./cursorstream'), | |
| 7 | timers = require('timers'), | |
| 8 | utils = require('./utils'); | |
| 9 | ||
| 10 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 11 | 1 | var processor = require('./utils').processor(); |
| 12 | ||
| 13 | /** | |
| 14 | * Constructor for a cursor object that handles all the operations on query result | |
| 15 | * using find. This cursor object is unidirectional and cannot traverse backwards. Clients should not be creating a cursor directly, | |
| 16 | * but use find to acquire a cursor. (INTERNAL TYPE) | |
| 17 | * | |
| 18 | * Options | |
| 19 | * - **skip** {Number} skip number of documents to skip. | |
| 20 | * - **limit** {Number}, limit the number of results to return. -1 has a special meaning and is used by Db.eval. A value of 1 will also be treated as if it were -1. | |
| 21 | * - **sort** {Array | Object}, set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc. | |
| 22 | * - **hint** {Object}, hint force the query to use a specific index. | |
| 23 | * - **explain** {Boolean}, explain return the explaination of the query. | |
| 24 | * - **snapshot** {Boolean}, snapshot Snapshot mode assures no duplicates are returned. | |
| 25 | * - **timeout** {Boolean}, timeout allow the query to timeout. | |
| 26 | * - **tailable** {Boolean}, tailable allow the cursor to be tailable. | |
| 27 | * - **awaitdata** {Boolean}, awaitdata allow the cursor to wait for data, only applicable for tailable cursor. | |
| 28 | * - **oplogReplay** {Boolean}, sets an internal flag, only applicable for tailable cursor. | |
| 29 | * - **batchSize** {Number}, batchSize the number of the subset of results to request the database to return for every request. This should initially be greater than 1 otherwise the database will automatically close the cursor. The batch size can be set to 1 with cursorInstance.batchSize after performing the initial query to the database. | |
| 30 | * - **raw** {Boolean}, raw return all query documents as raw buffers (default false). | |
| 31 | * - **read** {Boolean}, read specify override of read from source (primary/secondary). | |
| 32 | * - **returnKey** {Boolean}, returnKey only return the index key. | |
| 33 | * - **maxScan** {Number}, maxScan limit the number of items to scan. | |
| 34 | * - **min** {Number}, min set index bounds. | |
| 35 | * - **max** {Number}, max set index bounds. | |
| 36 | * - **maxTimeMS** {Number}, number of miliseconds to wait before aborting the query. | |
| 37 | * - **showDiskLoc** {Boolean}, showDiskLoc show disk location of results. | |
| 38 | * - **comment** {String}, comment you can put a $comment field on a query to make looking in the profiler logs simpler. | |
| 39 | * - **numberOfRetries** {Number}, numberOfRetries if using awaidata specifies the number of times to retry on timeout. | |
| 40 | * - **dbName** {String}, dbName override the default dbName. | |
| 41 | * - **tailableRetryInterval** {Number}, tailableRetryInterval specify the miliseconds between getMores on tailable cursor. | |
| 42 | * - **exhaust** {Boolean}, exhaust have the server send all the documents at once as getMore packets. | |
| 43 | * - **partial** {Boolean}, partial have the sharded system return a partial result from mongos. | |
| 44 | * | |
| 45 | * @class Represents a Cursor. | |
| 46 | * @param {Db} db the database object to work with. | |
| 47 | * @param {Collection} collection the collection to query. | |
| 48 | * @param {Object} selector the query selector. | |
| 49 | * @param {Object} fields an object containing what fields to include or exclude from objects returned. | |
| 50 | * @param {Object} [options] additional options for the collection. | |
| 51 | */ | |
| 52 | 1 | function Cursor(db, collection, selector, fields, options) { |
| 53 | 0 | this.db = db; |
| 54 | 0 | this.collection = collection; |
| 55 | 0 | this.selector = selector; |
| 56 | 0 | this.fields = fields; |
| 57 | 0 | options = !options ? {} : options; |
| 58 | ||
| 59 | 0 | this.skipValue = options.skip == null ? 0 : options.skip; |
| 60 | 0 | this.limitValue = options.limit == null ? 0 : options.limit; |
| 61 | 0 | this.sortValue = options.sort; |
| 62 | 0 | this.hint = options.hint; |
| 63 | 0 | this.explainValue = options.explain; |
| 64 | 0 | this.snapshot = options.snapshot; |
| 65 | 0 | this.timeout = options.timeout == null ? true : options.timeout; |
| 66 | 0 | this.tailable = options.tailable; |
| 67 | 0 | this.awaitdata = options.awaitdata; |
| 68 | 0 | this.oplogReplay = options.oplogReplay; |
| 69 | 0 | this.numberOfRetries = options.numberOfRetries == null ? 5 : options.numberOfRetries; |
| 70 | 0 | this.currentNumberOfRetries = this.numberOfRetries; |
| 71 | 0 | this.batchSizeValue = options.batchSize == null ? 0 : options.batchSize; |
| 72 | 0 | this.raw = options.raw == null ? false : options.raw; |
| 73 | 0 | this.read = options.read == null ? ReadPreference.PRIMARY : options.read; |
| 74 | 0 | this.returnKey = options.returnKey; |
| 75 | 0 | this.maxScan = options.maxScan; |
| 76 | 0 | this.min = options.min; |
| 77 | 0 | this.max = options.max; |
| 78 | 0 | this.showDiskLoc = options.showDiskLoc; |
| 79 | 0 | this.comment = options.comment; |
| 80 | 0 | this.tailableRetryInterval = options.tailableRetryInterval || 100; |
| 81 | 0 | this.exhaust = options.exhaust || false; |
| 82 | 0 | this.partial = options.partial || false; |
| 83 | 0 | this.slaveOk = options.slaveOk || false; |
| 84 | 0 | this.maxTimeMSValue = options.maxTimeMS; |
| 85 | ||
| 86 | 0 | this.totalNumberOfRecords = 0; |
| 87 | 0 | this.items = []; |
| 88 | 0 | this.cursorId = Long.fromInt(0); |
| 89 | ||
| 90 | // This name | |
| 91 | 0 | this.dbName = options.dbName; |
| 92 | ||
| 93 | // State variables for the cursor | |
| 94 | 0 | this.state = Cursor.INIT; |
| 95 | // Keep track of the current query run | |
| 96 | 0 | this.queryRun = false; |
| 97 | 0 | this.getMoreTimer = false; |
| 98 | ||
| 99 | // If we are using a specific db execute against it | |
| 100 | 0 | if(this.dbName != null) { |
| 101 | 0 | this.collectionName = this.dbName + "." + this.collection.collectionName; |
| 102 | } else { | |
| 103 | 0 | this.collectionName = (this.db.databaseName ? this.db.databaseName + "." : '') + this.collection.collectionName; |
| 104 | } | |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Resets this cursor to its initial state. All settings like the query string, | |
| 109 | * tailable, batchSizeValue, skipValue and limits are preserved. | |
| 110 | * | |
| 111 | * @return {Cursor} returns itself with rewind applied. | |
| 112 | * @api public | |
| 113 | */ | |
| 114 | 1 | Cursor.prototype.rewind = function() { |
| 115 | 0 | var self = this; |
| 116 | ||
| 117 | 0 | if (self.state != Cursor.INIT) { |
| 118 | 0 | if (self.state != Cursor.CLOSED) { |
| 119 | 0 | self.close(function() {}); |
| 120 | } | |
| 121 | ||
| 122 | 0 | self.numberOfReturned = 0; |
| 123 | 0 | self.totalNumberOfRecords = 0; |
| 124 | 0 | self.items = []; |
| 125 | 0 | self.cursorId = Long.fromInt(0); |
| 126 | 0 | self.state = Cursor.INIT; |
| 127 | 0 | self.queryRun = false; |
| 128 | } | |
| 129 | ||
| 130 | 0 | return self; |
| 131 | }; | |
| 132 | ||
| 133 | ||
| 134 | /** | |
| 135 | * Returns an array of documents. The caller is responsible for making sure that there | |
| 136 | * is enough memory to store the results. Note that the array only contain partial | |
| 137 | * results when this cursor had been previouly accessed. In that case, | |
| 138 | * cursor.rewind() can be used to reset the cursor. | |
| 139 | * | |
| 140 | * @param {Function} callback This will be called after executing this method successfully. The first parameter will contain the Error object if an error occured, or null otherwise. The second parameter will contain an array of BSON deserialized objects as a result of the query. | |
| 141 | * @return {null} | |
| 142 | * @api public | |
| 143 | */ | |
| 144 | 1 | Cursor.prototype.toArray = function(callback) { |
| 145 | 0 | var self = this; |
| 146 | ||
| 147 | 0 | if(!callback) { |
| 148 | 0 | throw new Error('callback is mandatory'); |
| 149 | } | |
| 150 | ||
| 151 | 0 | if(this.tailable) { |
| 152 | 0 | callback(new Error("Tailable cursor cannot be converted to array"), null); |
| 153 | 0 | } else if(this.state != Cursor.CLOSED) { |
| 154 | // return toArrayExhaust(self, callback); | |
| 155 | // If we are using exhaust we can't use the quick fire method | |
| 156 | 0 | if(self.exhaust) return toArrayExhaust(self, callback); |
| 157 | // Quick fire using trampoline to avoid nextTick | |
| 158 | 0 | self.nextObject({noReturn: true}, function(err, result) { |
| 159 | 0 | if(err) return callback(utils.toError(err), null); |
| 160 | 0 | if(self.cursorId.toString() == "0") { |
| 161 | 0 | self.state = Cursor.CLOSED; |
| 162 | 0 | return callback(null, self.items); |
| 163 | } | |
| 164 | ||
| 165 | // Let's issue getMores until we have no more records waiting | |
| 166 | 0 | getAllByGetMore(self, function(err, done) { |
| 167 | 0 | self.state = Cursor.CLOSED; |
| 168 | 0 | if(err) return callback(utils.toError(err), null); |
| 169 | // Let's release the internal list | |
| 170 | 0 | var items = self.items; |
| 171 | 0 | self.items = null; |
| 172 | // Return all the items | |
| 173 | 0 | callback(null, items); |
| 174 | }); | |
| 175 | }) | |
| 176 | ||
| 177 | } else { | |
| 178 | 0 | callback(new Error("Cursor is closed"), null); |
| 179 | } | |
| 180 | } | |
| 181 | ||
| 182 | 1 | var toArrayExhaust = function(self, callback) { |
| 183 | 0 | var items = []; |
| 184 | ||
| 185 | 0 | self.each(function(err, item) { |
| 186 | 0 | if(err != null) { |
| 187 | 0 | return callback(utils.toError(err), null); |
| 188 | } | |
| 189 | ||
| 190 | 0 | if(item != null && Array.isArray(items)) { |
| 191 | 0 | items.push(item); |
| 192 | } else { | |
| 193 | 0 | var resultItems = items; |
| 194 | 0 | items = null; |
| 195 | 0 | self.items = []; |
| 196 | 0 | callback(null, resultItems); |
| 197 | } | |
| 198 | }); | |
| 199 | } | |
| 200 | ||
| 201 | 1 | var getAllByGetMore = function(self, callback) { |
| 202 | 0 | getMore(self, {noReturn: true}, function(err, result) { |
| 203 | 0 | if(err) return callback(utils.toError(err)); |
| 204 | 0 | if(result == null) return callback(null, null); |
| 205 | 0 | if(self.cursorId.toString() == "0") return callback(null, null); |
| 206 | 0 | getAllByGetMore(self, callback); |
| 207 | }) | |
| 208 | }; | |
| 209 | ||
| 210 | /** | |
| 211 | * Iterates over all the documents for this cursor. As with **{cursor.toArray}**, | |
| 212 | * not all of the elements will be iterated if this cursor had been previouly accessed. | |
| 213 | * In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike | |
| 214 | * **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements | |
| 215 | * at any given time if batch size is specified. Otherwise, the caller is responsible | |
| 216 | * for making sure that the entire result can fit the memory. | |
| 217 | * | |
| 218 | * @param {Function} callback this will be called for while iterating every document of the query result. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the document. | |
| 219 | * @return {null} | |
| 220 | * @api public | |
| 221 | */ | |
| 222 | 1 | Cursor.prototype.each = function(callback) { |
| 223 | 0 | var self = this; |
| 224 | 0 | var fn; |
| 225 | ||
| 226 | 0 | if (!callback) { |
| 227 | 0 | throw new Error('callback is mandatory'); |
| 228 | } | |
| 229 | ||
| 230 | 0 | if(this.state != Cursor.CLOSED) { |
| 231 | // If we are using exhaust we can't use the quick fire method | |
| 232 | 0 | if(self.exhaust) return eachExhaust(self, callback); |
| 233 | // Quick fire using trampoline to avoid nextTick | |
| 234 | 0 | if(this.items.length > 0) { |
| 235 | // Trampoline all the entries | |
| 236 | 0 | while(fn = loop(self, callback)) fn(self, callback); |
| 237 | // Call each again | |
| 238 | 0 | self.each(callback); |
| 239 | } else { | |
| 240 | 0 | self.nextObject(function(err, item) { |
| 241 | ||
| 242 | 0 | if(err) { |
| 243 | 0 | self.state = Cursor.CLOSED; |
| 244 | 0 | return callback(utils.toError(err), item); |
| 245 | } | |
| 246 | ||
| 247 | 0 | if(item == null) return callback(null, null); |
| 248 | 0 | callback(null, item); |
| 249 | 0 | self.each(callback); |
| 250 | }) | |
| 251 | } | |
| 252 | } else { | |
| 253 | 0 | callback(new Error("Cursor is closed"), null); |
| 254 | } | |
| 255 | }; | |
| 256 | ||
| 257 | // Special for exhaust command as we don't initiate the actual result sets | |
| 258 | // the server just sends them as they arrive meaning we need to get the IO event | |
| 259 | // loop happen so we can receive more data from the socket or we return to early | |
| 260 | // after the first fetch and loose all the incoming getMore's automatically issued | |
| 261 | // from the server. | |
| 262 | 1 | var eachExhaust = function(self, callback) { |
| 263 | //FIX: stack overflow (on deep callback) (cred: https://github.com/limp/node-mongodb-native/commit/27da7e4b2af02035847f262b29837a94bbbf6ce2) | |
| 264 | 0 | processor(function(){ |
| 265 | // Fetch the next object until there is no more objects | |
| 266 | 0 | self.nextObject(function(err, item) { |
| 267 | 0 | if(err != null) return callback(err, null); |
| 268 | 0 | if(item != null) { |
| 269 | 0 | callback(null, item); |
| 270 | 0 | eachExhaust(self, callback); |
| 271 | } else { | |
| 272 | // Close the cursor if done | |
| 273 | 0 | self.state = Cursor.CLOSED; |
| 274 | 0 | callback(err, null); |
| 275 | } | |
| 276 | }); | |
| 277 | }); | |
| 278 | } | |
| 279 | ||
| 280 | // Trampoline emptying the number of retrieved items | |
| 281 | // without incurring a nextTick operation | |
| 282 | 1 | var loop = function(self, callback) { |
| 283 | // No more items we are done | |
| 284 | 0 | if(self.items.length == 0) return; |
| 285 | // Get the next document | |
| 286 | 0 | var doc = self.items.shift(); |
| 287 | // Callback | |
| 288 | 0 | callback(null, doc); |
| 289 | // Loop | |
| 290 | 0 | return loop; |
| 291 | } | |
| 292 | ||
| 293 | /** | |
| 294 | * Determines how many result the query for this cursor will return | |
| 295 | * | |
| 296 | * @param {Boolean} applySkipLimit if set to true will apply the skip and limits set on the cursor. Defaults to false. | |
| 297 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the number of results or null if an error occured. | |
| 298 | * @return {null} | |
| 299 | * @api public | |
| 300 | */ | |
| 301 | 1 | Cursor.prototype.count = function(applySkipLimit, callback) { |
| 302 | 0 | if(typeof applySkipLimit == 'function') { |
| 303 | 0 | callback = applySkipLimit; |
| 304 | 0 | applySkipLimit = false; |
| 305 | } | |
| 306 | ||
| 307 | 0 | var options = {}; |
| 308 | 0 | if(applySkipLimit) { |
| 309 | 0 | if(typeof this.skipValue == 'number') options.skip = this.skipValue; |
| 310 | 0 | if(typeof this.limitValue == 'number') options.limit = this.limitValue; |
| 311 | } | |
| 312 | ||
| 313 | // If maxTimeMS set | |
| 314 | 0 | if(typeof this.maxTimeMSValue == 'number') options.maxTimeMS = this.maxTimeMSValue; |
| 315 | ||
| 316 | // Call count command | |
| 317 | 0 | this.collection.count(this.selector, options, callback); |
| 318 | }; | |
| 319 | ||
| 320 | /** | |
| 321 | * Sets the sort parameter of this cursor to the given value. | |
| 322 | * | |
| 323 | * This method has the following method signatures: | |
| 324 | * (keyOrList, callback) | |
| 325 | * (keyOrList, direction, callback) | |
| 326 | * | |
| 327 | * @param {String|Array|Object} keyOrList This can be a string or an array. If passed as a string, the string will be the field to sort. If passed an array, each element will represent a field to be sorted and should be an array that contains the format [string, direction]. | |
| 328 | * @param {String|Number} direction this determines how the results are sorted. "asc", "ascending" or 1 for asceding order while "desc", "desceding or -1 for descending order. Note that the strings are case insensitive. | |
| 329 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 330 | * @return {Cursor} an instance of this object. | |
| 331 | * @api public | |
| 332 | */ | |
| 333 | 1 | Cursor.prototype.sort = function(keyOrList, direction, callback) { |
| 334 | 0 | callback = callback || function(){}; |
| 335 | 0 | if(typeof direction === "function") { callback = direction; direction = null; } |
| 336 | ||
| 337 | 0 | if(this.tailable) { |
| 338 | 0 | callback(new Error("Tailable cursor doesn't support sorting"), null); |
| 339 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 340 | 0 | callback(new Error("Cursor is closed"), null); |
| 341 | } else { | |
| 342 | 0 | var order = keyOrList; |
| 343 | ||
| 344 | 0 | if(direction != null) { |
| 345 | 0 | order = [[keyOrList, direction]]; |
| 346 | } | |
| 347 | ||
| 348 | 0 | this.sortValue = order; |
| 349 | 0 | callback(null, this); |
| 350 | } | |
| 351 | 0 | return this; |
| 352 | }; | |
| 353 | ||
| 354 | /** | |
| 355 | * Sets the limit parameter of this cursor to the given value. | |
| 356 | * | |
| 357 | * @param {Number} limit the new limit. | |
| 358 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 359 | * @return {Cursor} an instance of this object. | |
| 360 | * @api public | |
| 361 | */ | |
| 362 | 1 | Cursor.prototype.limit = function(limit, callback) { |
| 363 | 0 | if(this.tailable) { |
| 364 | 0 | if(callback) { |
| 365 | 0 | callback(new Error("Tailable cursor doesn't support limit"), null); |
| 366 | } else { | |
| 367 | 0 | throw new Error("Tailable cursor doesn't support limit"); |
| 368 | } | |
| 369 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 370 | 0 | if(callback) { |
| 371 | 0 | callback(new Error("Cursor is closed"), null); |
| 372 | } else { | |
| 373 | 0 | throw new Error("Cursor is closed"); |
| 374 | } | |
| 375 | } else { | |
| 376 | 0 | if(limit != null && limit.constructor != Number) { |
| 377 | 0 | if(callback) { |
| 378 | 0 | callback(new Error("limit requires an integer"), null); |
| 379 | } else { | |
| 380 | 0 | throw new Error("limit requires an integer"); |
| 381 | } | |
| 382 | } else { | |
| 383 | 0 | this.limitValue = limit; |
| 384 | 0 | if(callback) return callback(null, this); |
| 385 | } | |
| 386 | } | |
| 387 | ||
| 388 | 0 | return this; |
| 389 | }; | |
| 390 | ||
| 391 | /** | |
| 392 | * Specifies a time limit for a query operation. After the specified | |
| 393 | * time is exceeded, the operation will be aborted and an error will be | |
| 394 | * returned to the client. If maxTimeMS is null, no limit is applied. | |
| 395 | * | |
| 396 | * @param {Number} maxTimeMS the maxTimeMS for the query. | |
| 397 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the limit given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 398 | * @return {Cursor} an instance of this object. | |
| 399 | * @api public | |
| 400 | */ | |
| 401 | 1 | Cursor.prototype.maxTimeMS = function(maxTimeMS, callback) { |
| 402 | 0 | if(typeof maxTimeMS != 'number') { |
| 403 | 0 | throw new Error("maxTimeMS must be a number"); |
| 404 | } | |
| 405 | ||
| 406 | // Save the maxTimeMS option | |
| 407 | 0 | this.maxTimeMSValue = maxTimeMS; |
| 408 | // Return the cursor for chaining | |
| 409 | 0 | return this; |
| 410 | }; | |
| 411 | ||
| 412 | /** | |
| 413 | * Sets the read preference for the cursor | |
| 414 | * | |
| 415 | * @param {String} the read preference for the cursor, one of Server.READ_PRIMARY, Server.READ_SECONDARY, Server.READ_SECONDARY_ONLY | |
| 416 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the read preference given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 417 | * @return {Cursor} an instance of this object. | |
| 418 | * @api public | |
| 419 | */ | |
| 420 | 1 | Cursor.prototype.setReadPreference = function(readPreference, tags, callback) { |
| 421 | 0 | if(typeof tags == 'function') callback = tags; |
| 422 | ||
| 423 | 0 | var _mode = readPreference != null && typeof readPreference == 'object' ? readPreference.mode : readPreference; |
| 424 | ||
| 425 | 0 | if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 426 | 0 | if(callback == null) throw new Error("Cannot change read preference on executed query or closed cursor"); |
| 427 | 0 | callback(new Error("Cannot change read preference on executed query or closed cursor")); |
| 428 | 0 | } else if(_mode != null && _mode != 'primary' |
| 429 | && _mode != 'secondaryOnly' && _mode != 'secondary' | |
| 430 | && _mode != 'nearest' && _mode != 'primaryPreferred' && _mode != 'secondaryPreferred') { | |
| 431 | 0 | if(callback == null) throw new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported"); |
| 432 | 0 | callback(new Error("only readPreference of primary, secondary, secondaryPreferred, primaryPreferred or nearest supported")); |
| 433 | } else { | |
| 434 | 0 | this.read = readPreference; |
| 435 | 0 | if(callback != null) callback(null, this); |
| 436 | } | |
| 437 | ||
| 438 | 0 | return this; |
| 439 | } | |
| 440 | ||
| 441 | /** | |
| 442 | * Sets the skip parameter of this cursor to the given value. | |
| 443 | * | |
| 444 | * @param {Number} skip the new skip value. | |
| 445 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the skip value given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 446 | * @return {Cursor} an instance of this object. | |
| 447 | * @api public | |
| 448 | */ | |
| 449 | 1 | Cursor.prototype.skip = function(skip, callback) { |
| 450 | 0 | callback = callback || function(){}; |
| 451 | ||
| 452 | 0 | if(this.tailable) { |
| 453 | 0 | callback(new Error("Tailable cursor doesn't support skip"), null); |
| 454 | 0 | } else if(this.queryRun == true || this.state == Cursor.CLOSED) { |
| 455 | 0 | callback(new Error("Cursor is closed"), null); |
| 456 | } else { | |
| 457 | 0 | if(skip != null && skip.constructor != Number) { |
| 458 | 0 | callback(new Error("skip requires an integer"), null); |
| 459 | } else { | |
| 460 | 0 | this.skipValue = skip; |
| 461 | 0 | callback(null, this); |
| 462 | } | |
| 463 | } | |
| 464 | ||
| 465 | 0 | return this; |
| 466 | }; | |
| 467 | ||
| 468 | /** | |
| 469 | * Sets the batch size parameter of this cursor to the given value. | |
| 470 | * | |
| 471 | * @param {Number} batchSize the new batch size. | |
| 472 | * @param {Function} [callback] this optional callback will be called after executing this method. The first parameter will contain an error object when the batchSize given is not a valid number or when the cursor is already closed while the second parameter will contain a reference to this object upon successful execution. | |
| 473 | * @return {Cursor} an instance of this object. | |
| 474 | * @api public | |
| 475 | */ | |
| 476 | 1 | Cursor.prototype.batchSize = function(batchSize, callback) { |
| 477 | 0 | if(this.state == Cursor.CLOSED) { |
| 478 | 0 | if(callback != null) { |
| 479 | 0 | return callback(new Error("Cursor is closed"), null); |
| 480 | } else { | |
| 481 | 0 | throw new Error("Cursor is closed"); |
| 482 | } | |
| 483 | 0 | } else if(batchSize != null && batchSize.constructor != Number) { |
| 484 | 0 | if(callback != null) { |
| 485 | 0 | return callback(new Error("batchSize requires an integer"), null); |
| 486 | } else { | |
| 487 | 0 | throw new Error("batchSize requires an integer"); |
| 488 | } | |
| 489 | } else { | |
| 490 | 0 | this.batchSizeValue = batchSize; |
| 491 | 0 | if(callback != null) return callback(null, this); |
| 492 | } | |
| 493 | ||
| 494 | 0 | return this; |
| 495 | }; | |
| 496 | ||
| 497 | /** | |
| 498 | * The limit used for the getMore command | |
| 499 | * | |
| 500 | * @return {Number} The number of records to request per batch. | |
| 501 | * @ignore | |
| 502 | * @api private | |
| 503 | */ | |
| 504 | 1 | var limitRequest = function(self) { |
| 505 | 0 | var requestedLimit = self.limitValue; |
| 506 | 0 | var absLimitValue = Math.abs(self.limitValue); |
| 507 | 0 | var absBatchValue = Math.abs(self.batchSizeValue); |
| 508 | ||
| 509 | 0 | if(absLimitValue > 0) { |
| 510 | 0 | if (absBatchValue > 0) { |
| 511 | 0 | requestedLimit = Math.min(absLimitValue, absBatchValue); |
| 512 | } | |
| 513 | } else { | |
| 514 | 0 | requestedLimit = self.batchSizeValue; |
| 515 | } | |
| 516 | ||
| 517 | 0 | return requestedLimit; |
| 518 | }; | |
| 519 | ||
| 520 | ||
| 521 | /** | |
| 522 | * Generates a QueryCommand object using the parameters of this cursor. | |
| 523 | * | |
| 524 | * @return {QueryCommand} The command object | |
| 525 | * @ignore | |
| 526 | * @api private | |
| 527 | */ | |
| 528 | 1 | var generateQueryCommand = function(self) { |
| 529 | // Unpack the options | |
| 530 | 0 | var queryOptions = QueryCommand.OPTS_NONE; |
| 531 | 0 | if(!self.timeout) { |
| 532 | 0 | queryOptions |= QueryCommand.OPTS_NO_CURSOR_TIMEOUT; |
| 533 | } | |
| 534 | ||
| 535 | 0 | if(self.tailable != null) { |
| 536 | 0 | queryOptions |= QueryCommand.OPTS_TAILABLE_CURSOR; |
| 537 | 0 | self.skipValue = self.limitValue = 0; |
| 538 | ||
| 539 | // if awaitdata is set | |
| 540 | 0 | if(self.awaitdata != null) { |
| 541 | 0 | queryOptions |= QueryCommand.OPTS_AWAIT_DATA; |
| 542 | } | |
| 543 | ||
| 544 | // This sets an internal undocumented flag. Clients should not depend on its | |
| 545 | // behavior! | |
| 546 | 0 | if(self.oplogReplay != null) { |
| 547 | 0 | queryOptions |= QueryCommand.OPTS_OPLOG_REPLAY; |
| 548 | } | |
| 549 | } | |
| 550 | ||
| 551 | 0 | if(self.exhaust) { |
| 552 | 0 | queryOptions |= QueryCommand.OPTS_EXHAUST; |
| 553 | } | |
| 554 | ||
| 555 | // Unpack the read preference to set slave ok correctly | |
| 556 | 0 | var read = self.read instanceof ReadPreference ? self.read.mode : self.read; |
| 557 | ||
| 558 | // if(self.read == 'secondary') | |
| 559 | 0 | if(read == ReadPreference.PRIMARY_PREFERRED |
| 560 | || read == ReadPreference.SECONDARY | |
| 561 | || read == ReadPreference.SECONDARY_PREFERRED | |
| 562 | || read == ReadPreference.NEAREST) { | |
| 563 | 0 | queryOptions |= QueryCommand.OPTS_SLAVE; |
| 564 | } | |
| 565 | ||
| 566 | // Override slaveOk from the user | |
| 567 | 0 | if(self.slaveOk) { |
| 568 | 0 | queryOptions |= QueryCommand.OPTS_SLAVE; |
| 569 | } | |
| 570 | ||
| 571 | 0 | if(self.partial) { |
| 572 | 0 | queryOptions |= QueryCommand.OPTS_PARTIAL; |
| 573 | } | |
| 574 | ||
| 575 | // limitValue of -1 is a special case used by Db#eval | |
| 576 | 0 | var numberToReturn = self.limitValue == -1 ? -1 : limitRequest(self); |
| 577 | ||
| 578 | // Check if we need a special selector | |
| 579 | 0 | if(self.sortValue != null || self.explainValue != null || self.hint != null || self.snapshot != null |
| 580 | || self.returnKey != null || self.maxScan != null || self.min != null || self.max != null | |
| 581 | || self.showDiskLoc != null || self.comment != null || typeof self.maxTimeMSValue == 'number') { | |
| 582 | ||
| 583 | // Build special selector | |
| 584 | 0 | var specialSelector = {'$query':self.selector}; |
| 585 | 0 | if(self.sortValue != null) specialSelector['orderby'] = utils.formattedOrderClause(self.sortValue); |
| 586 | 0 | if(self.hint != null && self.hint.constructor == Object) specialSelector['$hint'] = self.hint; |
| 587 | 0 | if(self.snapshot != null) specialSelector['$snapshot'] = true; |
| 588 | 0 | if(self.returnKey != null) specialSelector['$returnKey'] = self.returnKey; |
| 589 | 0 | if(self.maxScan != null) specialSelector['$maxScan'] = self.maxScan; |
| 590 | 0 | if(self.min != null) specialSelector['$min'] = self.min; |
| 591 | 0 | if(self.max != null) specialSelector['$max'] = self.max; |
| 592 | 0 | if(self.showDiskLoc != null) specialSelector['$showDiskLoc'] = self.showDiskLoc; |
| 593 | 0 | if(self.comment != null) specialSelector['$comment'] = self.comment; |
| 594 | ||
| 595 | // If we are querying the $cmd collection we need to add maxTimeMS as a field | |
| 596 | // otherwise for a normal query it's a "special selector" $maxTimeMS | |
| 597 | 0 | if(typeof self.maxTimeMSValue == 'number' |
| 598 | && self.collectionName.indexOf('.$cmd') != -1) { | |
| 599 | 0 | specialSelector['maxTimeMS'] = self.maxTimeMSValue; |
| 600 | 0 | } else if(typeof self.maxTimeMSValue == 'number' |
| 601 | && self.collectionName.indexOf('.$cmd') == -1) { | |
| 602 | 0 | specialSelector['$maxTimeMS'] = self.maxTimeMSValue; |
| 603 | } | |
| 604 | ||
| 605 | // If we have explain set only return a single document with automatic cursor close | |
| 606 | 0 | if(self.explainValue != null) { |
| 607 | 0 | numberToReturn = (-1)*Math.abs(numberToReturn); |
| 608 | 0 | specialSelector['$explain'] = true; |
| 609 | } | |
| 610 | ||
| 611 | // Return the query | |
| 612 | 0 | return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, specialSelector, self.fields); |
| 613 | } else { | |
| 614 | 0 | return new QueryCommand(self.db, self.collectionName, queryOptions, self.skipValue, numberToReturn, self.selector, self.fields); |
| 615 | } | |
| 616 | }; | |
| 617 | ||
| 618 | /** | |
| 619 | * @return {Object} Returns an object containing the sort value of this cursor with | |
| 620 | * the proper formatting that can be used internally in this cursor. | |
| 621 | * @ignore | |
| 622 | * @api private | |
| 623 | */ | |
| 624 | 1 | Cursor.prototype.formattedOrderClause = function() { |
| 625 | 0 | return utils.formattedOrderClause(this.sortValue); |
| 626 | }; | |
| 627 | ||
| 628 | /** | |
| 629 | * Converts the value of the sort direction into its equivalent numerical value. | |
| 630 | * | |
| 631 | * @param sortDirection {String|number} Range of acceptable values: | |
| 632 | * 'ascending', 'descending', 'asc', 'desc', 1, -1 | |
| 633 | * | |
| 634 | * @return {number} The equivalent numerical value | |
| 635 | * @throws Error if the given sortDirection is invalid | |
| 636 | * @ignore | |
| 637 | * @api private | |
| 638 | */ | |
| 639 | 1 | Cursor.prototype.formatSortValue = function(sortDirection) { |
| 640 | 0 | return utils.formatSortValue(sortDirection); |
| 641 | }; | |
| 642 | ||
| 643 | /** | |
| 644 | * Gets the next document from the cursor. | |
| 645 | * | |
| 646 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. | |
| 647 | * @api public | |
| 648 | */ | |
| 649 | 1 | Cursor.prototype.nextObject = function(options, callback) { |
| 650 | 0 | var self = this; |
| 651 | ||
| 652 | 0 | if(typeof options == 'function') { |
| 653 | 0 | callback = options; |
| 654 | 0 | options = {}; |
| 655 | } | |
| 656 | ||
| 657 | 0 | if(self.state == Cursor.INIT) { |
| 658 | 0 | var cmd; |
| 659 | 0 | try { |
| 660 | 0 | cmd = generateQueryCommand(self); |
| 661 | } catch (err) { | |
| 662 | 0 | return callback(err, null); |
| 663 | } | |
| 664 | ||
| 665 | // No need to check the keys | |
| 666 | 0 | var queryOptions = {exhaust: self.exhaust |
| 667 | , raw:self.raw | |
| 668 | , read:self.read | |
| 669 | , connection:self.connection | |
| 670 | , checkKeys: false}; | |
| 671 | ||
| 672 | // Execute command | |
| 673 | 0 | var commandHandler = function(err, result) { |
| 674 | // If on reconnect, the command got given a different connection, switch | |
| 675 | // the whole cursor to it. | |
| 676 | 0 | self.connection = queryOptions.connection; |
| 677 | 0 | self.state = Cursor.OPEN; |
| 678 | 0 | if(err != null && result == null) return callback(utils.toError(err), null); |
| 679 | ||
| 680 | 0 | if(err == null && (result == null || result.documents == null || !Array.isArray(result.documents))) { |
| 681 | 0 | return self.close(function() {callback(new Error("command failed to return results"), null);}); |
| 682 | } | |
| 683 | ||
| 684 | 0 | if(err == null && result && result.documents[0] && result.documents[0]['$err']) { |
| 685 | 0 | return self.close(function() {callback(utils.toError(result.documents[0]['$err']), null);}); |
| 686 | } | |
| 687 | ||
| 688 | 0 | if(err == null && result && result.documents[0] && result.documents[0]['errmsg']) { |
| 689 | 0 | return self.close(function() {callback(utils.toError(result.documents[0]), null);}); |
| 690 | } | |
| 691 | ||
| 692 | 0 | self.queryRun = true; |
| 693 | 0 | self.state = Cursor.OPEN; // Adjust the state of the cursor |
| 694 | 0 | self.cursorId = result.cursorId; |
| 695 | 0 | self.totalNumberOfRecords = result.numberReturned; |
| 696 | ||
| 697 | // Add the new documents to the list of items, using forloop to avoid | |
| 698 | // new array allocations and copying | |
| 699 | 0 | for(var i = 0; i < result.documents.length; i++) { |
| 700 | 0 | self.items.push(result.documents[i]); |
| 701 | } | |
| 702 | ||
| 703 | // If we have noReturn set just return (not modifying the internal item list) | |
| 704 | // used for toArray | |
| 705 | 0 | if(options.noReturn) { |
| 706 | 0 | return callback(null, true); |
| 707 | } | |
| 708 | ||
| 709 | // Ignore callbacks until the cursor is dead for exhausted | |
| 710 | 0 | if(self.exhaust && result.cursorId.toString() == "0") { |
| 711 | 0 | self.nextObject(callback); |
| 712 | 0 | } else if(self.exhaust == false || self.exhaust == null) { |
| 713 | 0 | self.nextObject(callback); |
| 714 | } | |
| 715 | }; | |
| 716 | ||
| 717 | // If we have no connection set on this cursor check one out | |
| 718 | 0 | if(self.connection == null) { |
| 719 | 0 | try { |
| 720 | 0 | self.connection = self.db.serverConfig.checkoutReader(this.read); |
| 721 | // Add to the query options | |
| 722 | 0 | queryOptions.connection = self.connection; |
| 723 | } catch(err) { | |
| 724 | 0 | return callback(utils.toError(err), null); |
| 725 | } | |
| 726 | } | |
| 727 | ||
| 728 | // Execute the command | |
| 729 | 0 | self.db._executeQueryCommand(cmd, queryOptions, commandHandler); |
| 730 | // Set the command handler to null | |
| 731 | 0 | commandHandler = null; |
| 732 | 0 | } else if(self.items.length) { |
| 733 | 0 | callback(null, self.items.shift()); |
| 734 | 0 | } else if(self.cursorId.greaterThan(Long.fromInt(0))) { |
| 735 | 0 | getMore(self, callback); |
| 736 | } else { | |
| 737 | // Force cursor to stay open | |
| 738 | 0 | return self.close(function() {callback(null, null);}); |
| 739 | } | |
| 740 | } | |
| 741 | ||
| 742 | /** | |
| 743 | * Gets more results from the database if any. | |
| 744 | * | |
| 745 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an error object on error while the second parameter will contain a document from the returned result or null if there are no more results. | |
| 746 | * @ignore | |
| 747 | * @api private | |
| 748 | */ | |
| 749 | 1 | var getMore = function(self, options, callback) { |
| 750 | 0 | var limit = 0; |
| 751 | ||
| 752 | 0 | if(typeof options == 'function') { |
| 753 | 0 | callback = options; |
| 754 | 0 | options = {}; |
| 755 | } | |
| 756 | ||
| 757 | 0 | if(self.state == Cursor.GET_MORE) return callback(null, null); |
| 758 | ||
| 759 | // Set get more in progress | |
| 760 | 0 | self.state = Cursor.GET_MORE; |
| 761 | ||
| 762 | // Set options | |
| 763 | 0 | if (!self.tailable && self.limitValue > 0) { |
| 764 | 0 | limit = self.limitValue - self.totalNumberOfRecords; |
| 765 | 0 | if (limit < 1) { |
| 766 | 0 | self.close(function() {callback(null, null);}); |
| 767 | 0 | return; |
| 768 | } | |
| 769 | } | |
| 770 | ||
| 771 | 0 | try { |
| 772 | 0 | var getMoreCommand = new GetMoreCommand( |
| 773 | self.db | |
| 774 | , self.collectionName | |
| 775 | , limitRequest(self) | |
| 776 | , self.cursorId | |
| 777 | ); | |
| 778 | ||
| 779 | // Set up options | |
| 780 | 0 | var command_options = {read: self.read, raw: self.raw, connection:self.connection }; |
| 781 | ||
| 782 | // Execute the command | |
| 783 | 0 | self.db._executeQueryCommand(getMoreCommand, command_options, function(err, result) { |
| 784 | 0 | var cbValue; |
| 785 | ||
| 786 | // Get more done | |
| 787 | 0 | self.state = Cursor.OPEN; |
| 788 | ||
| 789 | 0 | if(err != null) { |
| 790 | 0 | self.state = Cursor.CLOSED; |
| 791 | 0 | return callback(utils.toError(err), null); |
| 792 | } | |
| 793 | ||
| 794 | // Ensure we get a valid result | |
| 795 | 0 | if(!result || !result.documents) { |
| 796 | 0 | self.state = Cursor.CLOSED; |
| 797 | 0 | return callback(utils.toError("command failed to return results"), null) |
| 798 | } | |
| 799 | ||
| 800 | // If the QueryFailure flag is set | |
| 801 | 0 | if((result.responseFlag & (1 << 1)) != 0) { |
| 802 | 0 | self.state = Cursor.CLOSED; |
| 803 | 0 | return callback(utils.toError("QueryFailure flag set on getmore command"), null); |
| 804 | } | |
| 805 | ||
| 806 | 0 | try { |
| 807 | 0 | var isDead = 1 === result.responseFlag && result.cursorId.isZero(); |
| 808 | ||
| 809 | 0 | self.cursorId = result.cursorId; |
| 810 | 0 | self.totalNumberOfRecords += result.numberReturned; |
| 811 | ||
| 812 | // Determine if there's more documents to fetch | |
| 813 | 0 | if(result.numberReturned > 0) { |
| 814 | 0 | if (self.limitValue > 0) { |
| 815 | 0 | var excessResult = self.totalNumberOfRecords - self.limitValue; |
| 816 | ||
| 817 | 0 | if (excessResult > 0) { |
| 818 | 0 | result.documents.splice(-1 * excessResult, excessResult); |
| 819 | } | |
| 820 | } | |
| 821 | ||
| 822 | // Reset the tries for awaitdata if we are using it | |
| 823 | 0 | self.currentNumberOfRetries = self.numberOfRetries; |
| 824 | // Get the documents | |
| 825 | 0 | for(var i = 0; i < result.documents.length; i++) { |
| 826 | 0 | self.items.push(result.documents[i]); |
| 827 | } | |
| 828 | ||
| 829 | // Don's shift a document out as we need it for toArray | |
| 830 | 0 | if(options.noReturn) { |
| 831 | 0 | cbValue = true; |
| 832 | } else { | |
| 833 | 0 | cbValue = self.items.shift(); |
| 834 | } | |
| 835 | 0 | } else if(self.tailable && !isDead && self.awaitdata) { |
| 836 | // Excute the tailable cursor once more, will timeout after ~4 sec if awaitdata used | |
| 837 | 0 | self.currentNumberOfRetries = self.currentNumberOfRetries - 1; |
| 838 | 0 | if(self.currentNumberOfRetries == 0) { |
| 839 | 0 | self.close(function() { |
| 840 | 0 | callback(new Error("tailable cursor timed out"), null); |
| 841 | }); | |
| 842 | } else { | |
| 843 | 0 | getMore(self, callback); |
| 844 | } | |
| 845 | 0 | } else if(self.tailable && !isDead) { |
| 846 | 0 | self.getMoreTimer = setTimeout(function() { getMore(self, callback); }, self.tailableRetryInterval); |
| 847 | } else { | |
| 848 | 0 | self.close(function() {callback(null, null); }); |
| 849 | } | |
| 850 | ||
| 851 | 0 | result = null; |
| 852 | } catch(err) { | |
| 853 | 0 | callback(utils.toError(err), null); |
| 854 | } | |
| 855 | 0 | if (cbValue != null) callback(null, cbValue); |
| 856 | }); | |
| 857 | ||
| 858 | 0 | getMoreCommand = null; |
| 859 | } catch(err) { | |
| 860 | // Get more done | |
| 861 | 0 | self.state = Cursor.OPEN; |
| 862 | ||
| 863 | 0 | var handleClose = function() { |
| 864 | 0 | callback(utils.toError(err), null); |
| 865 | }; | |
| 866 | ||
| 867 | 0 | self.close(handleClose); |
| 868 | 0 | handleClose = null; |
| 869 | } | |
| 870 | } | |
| 871 | ||
| 872 | /** | |
| 873 | * Gets a detailed information about how the query is performed on this cursor and how | |
| 874 | * long it took the database to process it. | |
| 875 | * | |
| 876 | * @param {Function} callback this will be called after executing this method. The first parameter will always be null while the second parameter will be an object containing the details. | |
| 877 | * @api public | |
| 878 | */ | |
| 879 | 1 | Cursor.prototype.explain = function(callback) { |
| 880 | 0 | var limit = (-1)*Math.abs(this.limitValue); |
| 881 | ||
| 882 | // Create a new cursor and fetch the plan | |
| 883 | 0 | var cursor = new Cursor(this.db, this.collection, this.selector, this.fields, { |
| 884 | skip: this.skipValue | |
| 885 | , limit:limit | |
| 886 | , sort: this.sortValue | |
| 887 | , hint: this.hint | |
| 888 | , explain: true | |
| 889 | , snapshot: this.snapshot | |
| 890 | , timeout: this.timeout | |
| 891 | , tailable: this.tailable | |
| 892 | , batchSize: this.batchSizeValue | |
| 893 | , slaveOk: this.slaveOk | |
| 894 | , raw: this.raw | |
| 895 | , read: this.read | |
| 896 | , returnKey: this.returnKey | |
| 897 | , maxScan: this.maxScan | |
| 898 | , min: this.min | |
| 899 | , max: this.max | |
| 900 | , showDiskLoc: this.showDiskLoc | |
| 901 | , comment: this.comment | |
| 902 | , awaitdata: this.awaitdata | |
| 903 | , oplogReplay: this.oplogReplay | |
| 904 | , numberOfRetries: this.numberOfRetries | |
| 905 | , dbName: this.dbName | |
| 906 | }); | |
| 907 | ||
| 908 | // Fetch the explaination document | |
| 909 | 0 | cursor.nextObject(function(err, item) { |
| 910 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 911 | // close the cursor | |
| 912 | 0 | cursor.close(function(err, result) { |
| 913 | 0 | if(err != null) return callback(utils.toError(err), null); |
| 914 | 0 | callback(null, item); |
| 915 | }); | |
| 916 | }); | |
| 917 | }; | |
| 918 | ||
| 919 | /** | |
| 920 | * Returns a Node ReadStream interface for this cursor. | |
| 921 | * | |
| 922 | * Options | |
| 923 | * - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting. | |
| 924 | * | |
| 925 | * @return {CursorStream} returns a stream object. | |
| 926 | * @api public | |
| 927 | */ | |
| 928 | 1 | Cursor.prototype.stream = function stream(options) { |
| 929 | 0 | return new CursorStream(this, options); |
| 930 | } | |
| 931 | ||
| 932 | /** | |
| 933 | * Close the cursor. | |
| 934 | * | |
| 935 | * @param {Function} callback this will be called after executing this method. The first parameter will always contain null while the second parameter will contain a reference to this cursor. | |
| 936 | * @return {null} | |
| 937 | * @api public | |
| 938 | */ | |
| 939 | 1 | Cursor.prototype.close = function(callback) { |
| 940 | 0 | var self = this |
| 941 | 0 | this.getMoreTimer && clearTimeout(this.getMoreTimer); |
| 942 | // Close the cursor if not needed | |
| 943 | 0 | if(this.cursorId instanceof Long && this.cursorId.greaterThan(Long.fromInt(0))) { |
| 944 | 0 | try { |
| 945 | 0 | var command = new KillCursorCommand(this.db, [this.cursorId]); |
| 946 | // Added an empty callback to ensure we don't throw any null exceptions | |
| 947 | 0 | this.db._executeQueryCommand(command, {read:self.read, raw:self.raw, connection:self.connection}); |
| 948 | } catch(err) {} | |
| 949 | } | |
| 950 | ||
| 951 | // Null out the connection | |
| 952 | 0 | self.connection = null; |
| 953 | // Reset cursor id | |
| 954 | 0 | this.cursorId = Long.fromInt(0); |
| 955 | // Set to closed status | |
| 956 | 0 | this.state = Cursor.CLOSED; |
| 957 | ||
| 958 | 0 | if(callback) { |
| 959 | 0 | callback(null, self); |
| 960 | 0 | self.items = []; |
| 961 | } | |
| 962 | ||
| 963 | 0 | return this; |
| 964 | }; | |
| 965 | ||
| 966 | /** | |
| 967 | * Check if the cursor is closed or open. | |
| 968 | * | |
| 969 | * @return {Boolean} returns the state of the cursor. | |
| 970 | * @api public | |
| 971 | */ | |
| 972 | 1 | Cursor.prototype.isClosed = function() { |
| 973 | 0 | return this.state == Cursor.CLOSED ? true : false; |
| 974 | }; | |
| 975 | ||
| 976 | /** | |
| 977 | * Init state | |
| 978 | * | |
| 979 | * @classconstant INIT | |
| 980 | **/ | |
| 981 | 1 | Cursor.INIT = 0; |
| 982 | ||
| 983 | /** | |
| 984 | * Cursor open | |
| 985 | * | |
| 986 | * @classconstant OPEN | |
| 987 | **/ | |
| 988 | 1 | Cursor.OPEN = 1; |
| 989 | ||
| 990 | /** | |
| 991 | * Cursor closed | |
| 992 | * | |
| 993 | * @classconstant CLOSED | |
| 994 | **/ | |
| 995 | 1 | Cursor.CLOSED = 2; |
| 996 | ||
| 997 | /** | |
| 998 | * Cursor performing a get more | |
| 999 | * | |
| 1000 | * @classconstant OPEN | |
| 1001 | **/ | |
| 1002 | 1 | Cursor.GET_MORE = 3; |
| 1003 | ||
| 1004 | /** | |
| 1005 | * @ignore | |
| 1006 | * @api private | |
| 1007 | */ | |
| 1008 | 1 | exports.Cursor = Cursor; |
| 1009 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var timers = require('timers'); |
| 2 | ||
| 3 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 4 | 1 | var processor = require('./utils').processor(); |
| 5 | ||
| 6 | /** | |
| 7 | * Module dependecies. | |
| 8 | */ | |
| 9 | 1 | var Stream = require('stream').Stream; |
| 10 | ||
| 11 | /** | |
| 12 | * CursorStream | |
| 13 | * | |
| 14 | * Returns a stream interface for the **cursor**. | |
| 15 | * | |
| 16 | * Options | |
| 17 | * - **transform** {Function} function of type function(object) { return transformed }, allows for transformation of data before emitting. | |
| 18 | * | |
| 19 | * Events | |
| 20 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 21 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 22 | * - **close** {function() {}} the end event triggers when there is no more documents available. | |
| 23 | * | |
| 24 | * @class Represents a CursorStream. | |
| 25 | * @param {Cursor} cursor a cursor object that the stream wraps. | |
| 26 | * @return {Stream} | |
| 27 | */ | |
| 28 | 1 | function CursorStream(cursor, options) { |
| 29 | 0 | if(!(this instanceof CursorStream)) return new CursorStream(cursor); |
| 30 | 0 | options = options ? options : {}; |
| 31 | ||
| 32 | 0 | Stream.call(this); |
| 33 | ||
| 34 | 0 | this.readable = true; |
| 35 | 0 | this.paused = false; |
| 36 | 0 | this._cursor = cursor; |
| 37 | 0 | this._destroyed = null; |
| 38 | 0 | this.options = options; |
| 39 | ||
| 40 | // give time to hook up events | |
| 41 | 0 | var self = this; |
| 42 | 0 | process.nextTick(function() { |
| 43 | 0 | self._init(); |
| 44 | }); | |
| 45 | } | |
| 46 | ||
| 47 | /** | |
| 48 | * Inherit from Stream | |
| 49 | * @ignore | |
| 50 | * @api private | |
| 51 | */ | |
| 52 | 1 | CursorStream.prototype.__proto__ = Stream.prototype; |
| 53 | ||
| 54 | /** | |
| 55 | * Flag stating whether or not this stream is readable. | |
| 56 | */ | |
| 57 | 1 | CursorStream.prototype.readable; |
| 58 | ||
| 59 | /** | |
| 60 | * Flag stating whether or not this stream is paused. | |
| 61 | */ | |
| 62 | 1 | CursorStream.prototype.paused; |
| 63 | ||
| 64 | /** | |
| 65 | * Initialize the cursor. | |
| 66 | * @ignore | |
| 67 | * @api private | |
| 68 | */ | |
| 69 | 1 | CursorStream.prototype._init = function () { |
| 70 | 0 | if (this._destroyed) return; |
| 71 | 0 | this._next(); |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Pull the next document from the cursor. | |
| 76 | * @ignore | |
| 77 | * @api private | |
| 78 | */ | |
| 79 | 1 | CursorStream.prototype._next = function () { |
| 80 | 0 | if(this.paused || this._destroyed) return; |
| 81 | ||
| 82 | 0 | var self = this; |
| 83 | // Get the next object | |
| 84 | 0 | processor(function() { |
| 85 | 0 | if(self.paused || self._destroyed) return; |
| 86 | ||
| 87 | 0 | self._cursor.nextObject(function (err, doc) { |
| 88 | 0 | self._onNextObject(err, doc); |
| 89 | }); | |
| 90 | }); | |
| 91 | } | |
| 92 | ||
| 93 | /** | |
| 94 | * Handle each document as its returned from the cursor. | |
| 95 | * @ignore | |
| 96 | * @api private | |
| 97 | */ | |
| 98 | 1 | CursorStream.prototype._onNextObject = function (err, doc) { |
| 99 | 0 | if(err) return this.destroy(err); |
| 100 | ||
| 101 | // when doc is null we hit the end of the cursor | |
| 102 | 0 | if(!doc && (this._cursor.state == 1 || this._cursor.state == 2)) { |
| 103 | 0 | this.emit('end') |
| 104 | 0 | return this.destroy(); |
| 105 | 0 | } else if(doc) { |
| 106 | 0 | var data = typeof this.options.transform == 'function' ? this.options.transform(doc) : doc; |
| 107 | 0 | this.emit('data', data); |
| 108 | 0 | this._next(); |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | /** | |
| 113 | * Pauses the stream. | |
| 114 | * | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | CursorStream.prototype.pause = function () { |
| 118 | 0 | this.paused = true; |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Resumes the stream. | |
| 123 | * | |
| 124 | * @api public | |
| 125 | */ | |
| 126 | 1 | CursorStream.prototype.resume = function () { |
| 127 | 0 | var self = this; |
| 128 | ||
| 129 | // Don't do anything if we are not paused | |
| 130 | 0 | if(!this.paused) return; |
| 131 | 0 | if(!this._cursor.state == 3) return; |
| 132 | ||
| 133 | 0 | process.nextTick(function() { |
| 134 | 0 | self.paused = false; |
| 135 | // Only trigger more fetching if the cursor is open | |
| 136 | 0 | self._next(); |
| 137 | }) | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Destroys the stream, closing the underlying | |
| 142 | * cursor. No more events will be emitted. | |
| 143 | * | |
| 144 | * @api public | |
| 145 | */ | |
| 146 | 1 | CursorStream.prototype.destroy = function (err) { |
| 147 | 0 | if (this._destroyed) return; |
| 148 | 0 | this._destroyed = true; |
| 149 | 0 | this.readable = false; |
| 150 | ||
| 151 | 0 | this._cursor.close(); |
| 152 | ||
| 153 | 0 | if(err) { |
| 154 | 0 | this.emit('error', err); |
| 155 | } | |
| 156 | ||
| 157 | 0 | this.emit('close'); |
| 158 | } | |
| 159 | ||
| 160 | // TODO - maybe implement the raw option to pass binary? | |
| 161 | //CursorStream.prototype.setEncoding = function () { | |
| 162 | //} | |
| 163 | ||
| 164 | 1 | module.exports = exports = CursorStream; |
| 165 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | * @ignore | |
| 4 | */ | |
| 5 | 1 | var QueryCommand = require('./commands/query_command').QueryCommand |
| 6 | , DbCommand = require('./commands/db_command').DbCommand | |
| 7 | , MongoReply = require('./responses/mongo_reply').MongoReply | |
| 8 | , Admin = require('./admin').Admin | |
| 9 | , Collection = require('./collection').Collection | |
| 10 | , Server = require('./connection/server').Server | |
| 11 | , ReplSet = require('./connection/repl_set/repl_set').ReplSet | |
| 12 | , ReadPreference = require('./connection/read_preference').ReadPreference | |
| 13 | , Mongos = require('./connection/mongos').Mongos | |
| 14 | , Cursor = require('./cursor').Cursor | |
| 15 | , EventEmitter = require('events').EventEmitter | |
| 16 | , inherits = require('util').inherits | |
| 17 | , crypto = require('crypto') | |
| 18 | , timers = require('timers') | |
| 19 | , utils = require('./utils') | |
| 20 | ||
| 21 | // Authentication methods | |
| 22 | , mongodb_cr_authenticate = require('./auth/mongodb_cr.js').authenticate | |
| 23 | , mongodb_gssapi_authenticate = require('./auth/mongodb_gssapi.js').authenticate | |
| 24 | , mongodb_sspi_authenticate = require('./auth/mongodb_sspi.js').authenticate | |
| 25 | , mongodb_plain_authenticate = require('./auth/mongodb_plain.js').authenticate | |
| 26 | , mongodb_x509_authenticate = require('./auth/mongodb_x509.js').authenticate; | |
| 27 | ||
| 28 | 1 | var hasKerberos = false; |
| 29 | // Check if we have a the kerberos library | |
| 30 | 1 | try { |
| 31 | 1 | require('kerberos'); |
| 32 | 1 | hasKerberos = true; |
| 33 | } catch(err) {} | |
| 34 | ||
| 35 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 36 | 1 | var processor = require('./utils').processor(); |
| 37 | ||
| 38 | /** | |
| 39 | * Create a new Db instance. | |
| 40 | * | |
| 41 | * Options | |
| 42 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 43 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 44 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 45 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 46 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 47 | * - **native_parser** {Boolean, default:false}, use c++ bson parser. | |
| 48 | * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. | |
| 49 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 50 | * - **serializeFunctions** {Boolean, default:false}, serialize functions. | |
| 51 | * - **raw** {Boolean, default:false}, perform operations using raw bson buffers. | |
| 52 | * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. | |
| 53 | * - **retryMiliSeconds** {Number, default:5000}, number of milliseconds between retries. | |
| 54 | * - **numberOfRetries** {Number, default:5}, number of retries off connection. | |
| 55 | * - **logger** {Object, default:null}, an object representing a logger that you want to use, needs to support functions debug, log, error **({error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}})**. | |
| 56 | * - **slaveOk** {Number, default:null}, force setting of SlaveOk flag on queries (only use when explicitly connecting to a secondary server). | |
| 57 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 58 | * - **bufferMaxEntries** {Boolean, default: -1}, sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited | |
| 59 | * | |
| 60 | * Deprecated Options | |
| 61 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 62 | * | |
| 63 | * @class Represents a Db | |
| 64 | * @param {String} databaseName name of the database. | |
| 65 | * @param {Object} serverConfig server config object. | |
| 66 | * @param {Object} [options] additional options for the collection. | |
| 67 | */ | |
| 68 | 1 | function Db(databaseName, serverConfig, options) { |
| 69 | 1 | if(!(this instanceof Db)) return new Db(databaseName, serverConfig, options); |
| 70 | 1 | EventEmitter.call(this); |
| 71 | 1 | var self = this; |
| 72 | 1 | this.databaseName = databaseName; |
| 73 | 1 | this.serverConfig = serverConfig; |
| 74 | 1 | this.options = options == null ? {} : options; |
| 75 | // State to check against if the user force closed db | |
| 76 | 1 | this._applicationClosed = false; |
| 77 | // Fetch the override flag if any | |
| 78 | 1 | var overrideUsedFlag = this.options['override_used_flag'] == null ? false : this.options['override_used_flag']; |
| 79 | ||
| 80 | // Verify that nobody is using this config | |
| 81 | 1 | if(!overrideUsedFlag && this.serverConfig != null && typeof this.serverConfig == 'object' && this.serverConfig._isUsed && this.serverConfig._isUsed()) { |
| 82 | 0 | throw new Error('A Server or ReplSet instance cannot be shared across multiple Db instances'); |
| 83 | 1 | } else if(!overrideUsedFlag && typeof this.serverConfig == 'object'){ |
| 84 | // Set being used | |
| 85 | 1 | this.serverConfig._used = true; |
| 86 | } | |
| 87 | ||
| 88 | // Allow slaveOk override | |
| 89 | 1 | this.slaveOk = this.options['slave_ok'] == null ? false : this.options['slave_ok']; |
| 90 | 1 | this.slaveOk = this.options['slaveOk'] == null ? this.slaveOk : this.options['slaveOk']; |
| 91 | ||
| 92 | // Number of operations to buffer before failure | |
| 93 | 1 | this.bufferMaxEntries = typeof this.options['bufferMaxEntries'] == 'number' ? this.options['bufferMaxEntries'] : -1; |
| 94 | ||
| 95 | // Ensure we have a valid db name | |
| 96 | 1 | validateDatabaseName(databaseName); |
| 97 | ||
| 98 | // Contains all the connections for the db | |
| 99 | 1 | try { |
| 100 | 1 | this.native_parser = this.options.native_parser; |
| 101 | // The bson lib | |
| 102 | 1 | var bsonLib = this.bsonLib = this.options.native_parser ? require('bson').BSONNative : require('bson').BSONPure; |
| 103 | // Fetch the serializer object | |
| 104 | 1 | var BSON = bsonLib.BSON; |
| 105 | ||
| 106 | // Create a new instance | |
| 107 | 1 | this.bson = new BSON([bsonLib.Long, bsonLib.ObjectID, bsonLib.Binary, bsonLib.Code, bsonLib.DBRef, bsonLib.Symbol, bsonLib.Double, bsonLib.Timestamp, bsonLib.MaxKey, bsonLib.MinKey]); |
| 108 | 1 | this.bson.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs; |
| 109 | ||
| 110 | // Backward compatibility to access types | |
| 111 | 1 | this.bson_deserializer = bsonLib; |
| 112 | 1 | this.bson_serializer = bsonLib; |
| 113 | ||
| 114 | // Add any overrides to the serializer and deserializer | |
| 115 | 1 | this.bson_deserializer.promoteLongs = this.options.promoteLongs == null ? true : this.options.promoteLongs; |
| 116 | } catch (err) { | |
| 117 | // If we tried to instantiate the native driver | |
| 118 | 0 | var msg = 'Native bson parser not compiled, please compile ' |
| 119 | + 'or avoid using native_parser=true'; | |
| 120 | 0 | throw Error(msg); |
| 121 | } | |
| 122 | ||
| 123 | // Internal state of the server | |
| 124 | 1 | this._state = 'disconnected'; |
| 125 | ||
| 126 | 1 | this.pkFactory = this.options.pk == null ? bsonLib.ObjectID : this.options.pk; |
| 127 | 1 | this.forceServerObjectId = this.options.forceServerObjectId != null ? this.options.forceServerObjectId : false; |
| 128 | ||
| 129 | // Added safe | |
| 130 | 1 | this.safe = this.options.safe == null ? false : this.options.safe; |
| 131 | ||
| 132 | // If we have not specified a "safe mode" we just print a warning to the console | |
| 133 | 1 | if(this.options.safe == null && this.options.w == null && this.options.journal == null && this.options.fsync == null) { |
| 134 | 0 | console.log("========================================================================================"); |
| 135 | 0 | console.log("= Please ensure that you set the default write concern for the database by setting ="); |
| 136 | 0 | console.log("= one of the options ="); |
| 137 | 0 | console.log("= ="); |
| 138 | 0 | console.log("= w: (value of > -1 or the string 'majority'), where < 1 means ="); |
| 139 | 0 | console.log("= no write acknowledgement ="); |
| 140 | 0 | console.log("= journal: true/false, wait for flush to journal before acknowledgement ="); |
| 141 | 0 | console.log("= fsync: true/false, wait for flush to file system before acknowledgement ="); |
| 142 | 0 | console.log("= ="); |
| 143 | 0 | console.log("= For backward compatibility safe is still supported and ="); |
| 144 | 0 | console.log("= allows values of [true | false | {j:true} | {w:n, wtimeout:n} | {fsync:true}] ="); |
| 145 | 0 | console.log("= the default value is false which means the driver receives does not ="); |
| 146 | 0 | console.log("= return the information of the success/error of the insert/update/remove ="); |
| 147 | 0 | console.log("= ="); |
| 148 | 0 | console.log("= ex: new Db(new Server('localhost', 27017), {safe:false}) ="); |
| 149 | 0 | console.log("= ="); |
| 150 | 0 | console.log("= http://www.mongodb.org/display/DOCS/getLastError+Command ="); |
| 151 | 0 | console.log("= ="); |
| 152 | 0 | console.log("= The default of no acknowledgement will change in the very near future ="); |
| 153 | 0 | console.log("= ="); |
| 154 | 0 | console.log("= This message will disappear when the default safe is set on the driver Db ="); |
| 155 | 0 | console.log("========================================================================================"); |
| 156 | } | |
| 157 | ||
| 158 | // Internal states variables | |
| 159 | 1 | this.notReplied ={}; |
| 160 | 1 | this.isInitializing = true; |
| 161 | 1 | this.openCalled = false; |
| 162 | ||
| 163 | // Command queue, keeps a list of incoming commands that need to be executed once the connection is up | |
| 164 | 1 | this.commands = []; |
| 165 | ||
| 166 | // Set up logger | |
| 167 | 1 | this.logger = this.options.logger != null |
| 168 | && (typeof this.options.logger.debug == 'function') | |
| 169 | && (typeof this.options.logger.error == 'function') | |
| 170 | && (typeof this.options.logger.log == 'function') | |
| 171 | ? this.options.logger : {error:function(message, object) {}, log:function(message, object) {}, debug:function(message, object) {}}; | |
| 172 | ||
| 173 | // Associate the logger with the server config | |
| 174 | 1 | this.serverConfig.logger = this.logger; |
| 175 | 1 | if(this.serverConfig.strategyInstance) this.serverConfig.strategyInstance.logger = this.logger; |
| 176 | 1 | this.tag = new Date().getTime(); |
| 177 | // Just keeps list of events we allow | |
| 178 | 1 | this.eventHandlers = {error:[], parseError:[], poolReady:[], message:[], close:[]}; |
| 179 | ||
| 180 | // Controls serialization options | |
| 181 | 1 | this.serializeFunctions = this.options.serializeFunctions != null ? this.options.serializeFunctions : false; |
| 182 | ||
| 183 | // Raw mode | |
| 184 | 1 | this.raw = this.options.raw != null ? this.options.raw : false; |
| 185 | ||
| 186 | // Record query stats | |
| 187 | 1 | this.recordQueryStats = this.options.recordQueryStats != null ? this.options.recordQueryStats : false; |
| 188 | ||
| 189 | // If we have server stats let's make sure the driver objects have it enabled | |
| 190 | 1 | if(this.recordQueryStats == true) { |
| 191 | 0 | this.serverConfig.enableRecordQueryStats(true); |
| 192 | } | |
| 193 | ||
| 194 | // Retry information | |
| 195 | 1 | this.retryMiliSeconds = this.options.retryMiliSeconds != null ? this.options.retryMiliSeconds : 1000; |
| 196 | 1 | this.numberOfRetries = this.options.numberOfRetries != null ? this.options.numberOfRetries : 60; |
| 197 | ||
| 198 | // Set default read preference if any | |
| 199 | 1 | this.readPreference = this.options.readPreference; |
| 200 | ||
| 201 | // Set read preference on serverConfig if none is set | |
| 202 | // but the db one was | |
| 203 | 1 | if(this.serverConfig.options.readPreference == null |
| 204 | && this.readPreference != null) { | |
| 205 | 0 | this.serverConfig.setReadPreference(this.readPreference); |
| 206 | } | |
| 207 | ||
| 208 | // Ensure we keep a reference to this db | |
| 209 | 1 | this.serverConfig._dbStore.add(this); |
| 210 | }; | |
| 211 | ||
| 212 | /** | |
| 213 | * @ignore | |
| 214 | */ | |
| 215 | 1 | function validateDatabaseName(databaseName) { |
| 216 | 1 | if(typeof databaseName !== 'string') throw new Error("database name must be a string"); |
| 217 | 1 | if(databaseName.length === 0) throw new Error("database name cannot be the empty string"); |
| 218 | 1 | if(databaseName == '$external') return; |
| 219 | ||
| 220 | 1 | var invalidChars = [" ", ".", "$", "/", "\\"]; |
| 221 | 1 | for(var i = 0; i < invalidChars.length; i++) { |
| 222 | 5 | if(databaseName.indexOf(invalidChars[i]) != -1) throw new Error("database names cannot contain the character '" + invalidChars[i] + "'"); |
| 223 | } | |
| 224 | } | |
| 225 | ||
| 226 | /** | |
| 227 | * @ignore | |
| 228 | */ | |
| 229 | 1 | inherits(Db, EventEmitter); |
| 230 | ||
| 231 | /** | |
| 232 | * Initialize the database connection. | |
| 233 | * | |
| 234 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the index information or null if an error occurred. | |
| 235 | * @return {null} | |
| 236 | * @api public | |
| 237 | */ | |
| 238 | 1 | Db.prototype.open = function(callback) { |
| 239 | 1 | var self = this; |
| 240 | ||
| 241 | // Check that the user has not called this twice | |
| 242 | 1 | if(this.openCalled) { |
| 243 | // Close db | |
| 244 | 0 | this.close(); |
| 245 | // Throw error | |
| 246 | 0 | throw new Error("db object already connecting, open cannot be called multiple times"); |
| 247 | } | |
| 248 | ||
| 249 | // If we have a specified read preference | |
| 250 | 1 | if(this.readPreference != null) this.serverConfig.setReadPreference(this.readPreference); |
| 251 | ||
| 252 | // Set that db has been opened | |
| 253 | 1 | this.openCalled = true; |
| 254 | ||
| 255 | // Set the status of the server | |
| 256 | 1 | self._state = 'connecting'; |
| 257 | ||
| 258 | // Set up connections | |
| 259 | 1 | if(self.serverConfig instanceof Server || self.serverConfig instanceof ReplSet || self.serverConfig instanceof Mongos) { |
| 260 | // Ensure we have the original options passed in for the server config | |
| 261 | 1 | var connect_options = {}; |
| 262 | 1 | for(var name in self.serverConfig.options) { |
| 263 | 2 | connect_options[name] = self.serverConfig.options[name] |
| 264 | } | |
| 265 | 1 | connect_options.firstCall = true; |
| 266 | ||
| 267 | // Attempt to connect | |
| 268 | 1 | self.serverConfig.connect(self, connect_options, function(err, result) { |
| 269 | 0 | if(err != null) { |
| 270 | // Close db to reset connection | |
| 271 | 0 | return self.close(function () { |
| 272 | // Return error from connection | |
| 273 | 0 | return callback(err, null); |
| 274 | }); | |
| 275 | } | |
| 276 | // Set the status of the server | |
| 277 | 0 | self._state = 'connected'; |
| 278 | // If we have queued up commands execute a command to trigger replays | |
| 279 | 0 | if(self.commands.length > 0) _execute_queued_command(self); |
| 280 | // Callback | |
| 281 | 0 | process.nextTick(function() { |
| 282 | 0 | try { |
| 283 | 0 | callback(null, self); |
| 284 | } catch(err) { | |
| 285 | 0 | self.close(); |
| 286 | 0 | throw err; |
| 287 | } | |
| 288 | }); | |
| 289 | }); | |
| 290 | } else { | |
| 291 | 0 | try { |
| 292 | 0 | callback(Error("Server parameter must be of type Server, ReplSet or Mongos"), null); |
| 293 | } catch(err) { | |
| 294 | 0 | self.close(); |
| 295 | 0 | throw err; |
| 296 | } | |
| 297 | } | |
| 298 | }; | |
| 299 | ||
| 300 | /** | |
| 301 | * Create a new Db instance sharing the current socket connections. | |
| 302 | * | |
| 303 | * @param {String} dbName the name of the database we want to use. | |
| 304 | * @return {Db} a db instance using the new database. | |
| 305 | * @api public | |
| 306 | */ | |
| 307 | 1 | Db.prototype.db = function(dbName) { |
| 308 | // Copy the options and add out internal override of the not shared flag | |
| 309 | 0 | var options = {}; |
| 310 | 0 | for(var key in this.options) { |
| 311 | 0 | options[key] = this.options[key]; |
| 312 | } | |
| 313 | ||
| 314 | // Add override flag | |
| 315 | 0 | options['override_used_flag'] = true; |
| 316 | // Check if the db already exists and reuse if it's the case | |
| 317 | 0 | var db = this.serverConfig._dbStore.fetch(dbName); |
| 318 | ||
| 319 | // Create a new instance | |
| 320 | 0 | if(!db) { |
| 321 | 0 | db = new Db(dbName, this.serverConfig, options); |
| 322 | } | |
| 323 | ||
| 324 | // Return the db object | |
| 325 | 0 | return db; |
| 326 | }; | |
| 327 | ||
| 328 | /** | |
| 329 | * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. | |
| 330 | * | |
| 331 | * @param {Boolean} [forceClose] connection can never be reused. | |
| 332 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results or null if an error occurred. | |
| 333 | * @return {null} | |
| 334 | * @api public | |
| 335 | */ | |
| 336 | 1 | Db.prototype.close = function(forceClose, callback) { |
| 337 | 0 | var self = this; |
| 338 | // Ensure we force close all connections | |
| 339 | 0 | this._applicationClosed = false; |
| 340 | ||
| 341 | 0 | if(typeof forceClose == 'function') { |
| 342 | 0 | callback = forceClose; |
| 343 | 0 | } else if(typeof forceClose == 'boolean') { |
| 344 | 0 | this._applicationClosed = forceClose; |
| 345 | } | |
| 346 | ||
| 347 | 0 | this.serverConfig.close(function(err, result) { |
| 348 | // You can reuse the db as everything is shut down | |
| 349 | 0 | self.openCalled = false; |
| 350 | // If we have a callback call it | |
| 351 | 0 | if(callback) callback(err, result); |
| 352 | }); | |
| 353 | }; | |
| 354 | ||
| 355 | /** | |
| 356 | * Access the Admin database | |
| 357 | * | |
| 358 | * @param {Function} [callback] returns the results. | |
| 359 | * @return {Admin} the admin db object. | |
| 360 | * @api public | |
| 361 | */ | |
| 362 | 1 | Db.prototype.admin = function(callback) { |
| 363 | 0 | if(callback == null) return new Admin(this); |
| 364 | 0 | callback(null, new Admin(this)); |
| 365 | }; | |
| 366 | ||
| 367 | /** | |
| 368 | * Returns a cursor to all the collection information. | |
| 369 | * | |
| 370 | * @param {String} [collectionName] the collection name we wish to retrieve the information from. | |
| 371 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the options or null if an error occurred. | |
| 372 | * @return {null} | |
| 373 | * @api public | |
| 374 | */ | |
| 375 | 1 | Db.prototype.collectionsInfo = function(collectionName, callback) { |
| 376 | 0 | if(callback == null && typeof collectionName == 'function') { callback = collectionName; collectionName = null; } |
| 377 | // Create selector | |
| 378 | 0 | var selector = {}; |
| 379 | // If we are limiting the access to a specific collection name | |
| 380 | 0 | if(collectionName != null) selector.name = this.databaseName + "." + collectionName; |
| 381 | ||
| 382 | // Return Cursor | |
| 383 | // callback for backward compatibility | |
| 384 | 0 | if(callback) { |
| 385 | 0 | callback(null, new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector)); |
| 386 | } else { | |
| 387 | 0 | return new Cursor(this, new Collection(this, DbCommand.SYSTEM_NAMESPACE_COLLECTION), selector); |
| 388 | } | |
| 389 | }; | |
| 390 | ||
| 391 | /** | |
| 392 | * Get the list of all collection names for the specified db | |
| 393 | * | |
| 394 | * Options | |
| 395 | * - **namesOnly** {String, default:false}, Return only the full collection namespace. | |
| 396 | * | |
| 397 | * @param {String} [collectionName] the collection name we wish to filter by. | |
| 398 | * @param {Object} [options] additional options during update. | |
| 399 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collection names or null if an error occurred. | |
| 400 | * @return {null} | |
| 401 | * @api public | |
| 402 | */ | |
| 403 | 1 | Db.prototype.collectionNames = function(collectionName, options, callback) { |
| 404 | 0 | var self = this; |
| 405 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 406 | 0 | callback = args.pop(); |
| 407 | 0 | collectionName = args.length ? args.shift() : null; |
| 408 | 0 | options = args.length ? args.shift() || {} : {}; |
| 409 | ||
| 410 | // Ensure no breaking behavior | |
| 411 | 0 | if(collectionName != null && typeof collectionName == 'object') { |
| 412 | 0 | options = collectionName; |
| 413 | 0 | collectionName = null; |
| 414 | } | |
| 415 | ||
| 416 | // Let's make our own callback to reuse the existing collections info method | |
| 417 | 0 | self.collectionsInfo(collectionName, function(err, cursor) { |
| 418 | 0 | if(err != null) return callback(err, null); |
| 419 | ||
| 420 | 0 | cursor.toArray(function(err, documents) { |
| 421 | 0 | if(err != null) return callback(err, null); |
| 422 | ||
| 423 | // List of result documents that have been filtered | |
| 424 | 0 | var filtered_documents = documents.filter(function(document) { |
| 425 | 0 | return !(document.name.indexOf(self.databaseName) == -1 || document.name.indexOf('$') != -1); |
| 426 | }); | |
| 427 | ||
| 428 | // If we are returning only the names | |
| 429 | 0 | if(options.namesOnly) { |
| 430 | 0 | filtered_documents = filtered_documents.map(function(document) { return document.name }); |
| 431 | } | |
| 432 | ||
| 433 | // Return filtered items | |
| 434 | 0 | callback(null, filtered_documents); |
| 435 | }); | |
| 436 | }); | |
| 437 | }; | |
| 438 | ||
| 439 | /** | |
| 440 | * Fetch a specific collection (containing the actual collection information). If the application does not use strict mode you can | |
| 441 | * can use it without a callback in the following way. var collection = db.collection('mycollection'); | |
| 442 | * | |
| 443 | * Options | |
| 444 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 445 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 446 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 447 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 448 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 449 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 450 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 451 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 452 | * - **strict**, (Boolean, default:false) returns an error if the collection does not exist | |
| 453 | * | |
| 454 | * Deprecated Options | |
| 455 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 456 | * | |
| 457 | * @param {String} collectionName the collection name we wish to access. | |
| 458 | * @param {Object} [options] returns option results. | |
| 459 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collection or null if an error occurred. | |
| 460 | * @return {null} | |
| 461 | * @api public | |
| 462 | */ | |
| 463 | 1 | Db.prototype.collection = function(collectionName, options, callback) { |
| 464 | 0 | var self = this; |
| 465 | 0 | if(typeof options === "function") { callback = options; options = {}; } |
| 466 | // Execute safe | |
| 467 | ||
| 468 | 0 | if(options && (options.strict)) { |
| 469 | 0 | self.collectionNames(collectionName, function(err, collections) { |
| 470 | 0 | if(err != null) return callback(err, null); |
| 471 | ||
| 472 | 0 | if(collections.length == 0) { |
| 473 | 0 | return callback(new Error("Collection " + collectionName + " does not exist. Currently in safe mode."), null); |
| 474 | } else { | |
| 475 | 0 | try { |
| 476 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 477 | } catch(err) { | |
| 478 | 0 | return callback(err, null); |
| 479 | } | |
| 480 | 0 | return callback(null, collection); |
| 481 | } | |
| 482 | }); | |
| 483 | } else { | |
| 484 | 0 | try { |
| 485 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 486 | } catch(err) { | |
| 487 | 0 | if(callback == null) { |
| 488 | 0 | throw err; |
| 489 | } else { | |
| 490 | 0 | return callback(err, null); |
| 491 | } | |
| 492 | } | |
| 493 | ||
| 494 | // If we have no callback return collection object | |
| 495 | 0 | return callback == null ? collection : callback(null, collection); |
| 496 | } | |
| 497 | }; | |
| 498 | ||
| 499 | /** | |
| 500 | * Fetch all collections for the current db. | |
| 501 | * | |
| 502 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the collections or null if an error occurred. | |
| 503 | * @return {null} | |
| 504 | * @api public | |
| 505 | */ | |
| 506 | 1 | Db.prototype.collections = function(callback) { |
| 507 | 0 | var self = this; |
| 508 | // Let's get the collection names | |
| 509 | 0 | self.collectionNames(function(err, documents) { |
| 510 | 0 | if(err != null) return callback(err, null); |
| 511 | 0 | var collections = []; |
| 512 | 0 | documents.forEach(function(document) { |
| 513 | 0 | collections.push(new Collection(self, document.name.replace(self.databaseName + ".", ''), self.pkFactory)); |
| 514 | }); | |
| 515 | // Return the collection objects | |
| 516 | 0 | callback(null, collections); |
| 517 | }); | |
| 518 | }; | |
| 519 | ||
| 520 | /** | |
| 521 | * Evaluate javascript on the server | |
| 522 | * | |
| 523 | * Options | |
| 524 | * - **nolock** {Boolean, default:false}, Tell MongoDB not to block on the evaulation of the javascript. | |
| 525 | * | |
| 526 | * @param {Code} code javascript to execute on server. | |
| 527 | * @param {Object|Array} [parameters] the parameters for the call. | |
| 528 | * @param {Object} [options] the options | |
| 529 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from eval or null if an error occurred. | |
| 530 | * @return {null} | |
| 531 | * @api public | |
| 532 | */ | |
| 533 | 1 | Db.prototype.eval = function(code, parameters, options, callback) { |
| 534 | // Unpack calls | |
| 535 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 536 | 0 | callback = args.pop(); |
| 537 | 0 | parameters = args.length ? args.shift() : parameters; |
| 538 | 0 | options = args.length ? args.shift() || {} : {}; |
| 539 | ||
| 540 | 0 | var finalCode = code; |
| 541 | 0 | var finalParameters = []; |
| 542 | // If not a code object translate to one | |
| 543 | 0 | if(!(finalCode instanceof this.bsonLib.Code)) { |
| 544 | 0 | finalCode = new this.bsonLib.Code(finalCode); |
| 545 | } | |
| 546 | ||
| 547 | // Ensure the parameters are correct | |
| 548 | 0 | if(parameters != null && parameters.constructor != Array && typeof parameters !== 'function') { |
| 549 | 0 | finalParameters = [parameters]; |
| 550 | 0 | } else if(parameters != null && parameters.constructor == Array && typeof parameters !== 'function') { |
| 551 | 0 | finalParameters = parameters; |
| 552 | } | |
| 553 | ||
| 554 | // Create execution selector | |
| 555 | 0 | var selector = {'$eval':finalCode, 'args':finalParameters}; |
| 556 | // Check if the nolock parameter is passed in | |
| 557 | 0 | if(options['nolock']) { |
| 558 | 0 | selector['nolock'] = options['nolock']; |
| 559 | } | |
| 560 | ||
| 561 | // Set primary read preference | |
| 562 | 0 | options.readPreference = ReadPreference.PRIMARY; |
| 563 | ||
| 564 | // Execute the eval | |
| 565 | 0 | this.collection(DbCommand.SYSTEM_COMMAND_COLLECTION).findOne(selector, options, function(err, result) { |
| 566 | 0 | if(err) return callback(err); |
| 567 | ||
| 568 | 0 | if(result && result.ok == 1) { |
| 569 | 0 | callback(null, result.retval); |
| 570 | 0 | } else if(result) { |
| 571 | 0 | callback(new Error("eval failed: " + result.errmsg), null); return; |
| 572 | } else { | |
| 573 | 0 | callback(err, result); |
| 574 | } | |
| 575 | }); | |
| 576 | }; | |
| 577 | ||
| 578 | /** | |
| 579 | * Dereference a dbref, against a db | |
| 580 | * | |
| 581 | * @param {DBRef} dbRef db reference object we wish to resolve. | |
| 582 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dereference or null if an error occurred. | |
| 583 | * @return {null} | |
| 584 | * @api public | |
| 585 | */ | |
| 586 | 1 | Db.prototype.dereference = function(dbRef, callback) { |
| 587 | 0 | var db = this; |
| 588 | // If we have a db reference then let's get the db first | |
| 589 | 0 | if(dbRef.db != null) db = this.db(dbRef.db); |
| 590 | // Fetch the collection and find the reference | |
| 591 | 0 | var collection = db.collection(dbRef.namespace); |
| 592 | 0 | collection.findOne({'_id':dbRef.oid}, function(err, result) { |
| 593 | 0 | callback(err, result); |
| 594 | }); | |
| 595 | }; | |
| 596 | ||
| 597 | /** | |
| 598 | * Logout user from server, fire off on all connections and remove all auth info | |
| 599 | * | |
| 600 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from logout or null if an error occurred. | |
| 601 | * @return {null} | |
| 602 | * @api public | |
| 603 | */ | |
| 604 | 1 | Db.prototype.logout = function(options, callback) { |
| 605 | 0 | var self = this; |
| 606 | // Unpack calls | |
| 607 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 608 | 0 | callback = args.pop(); |
| 609 | 0 | options = args.length ? args.shift() || {} : {}; |
| 610 | ||
| 611 | // Number of connections we need to logout from | |
| 612 | 0 | var numberOfConnections = this.serverConfig.allRawConnections().length; |
| 613 | ||
| 614 | // Let's generate the logout command object | |
| 615 | 0 | var logoutCommand = DbCommand.logoutCommand(self, {logout:1}, options); |
| 616 | 0 | self._executeQueryCommand(logoutCommand, {onAll:true}, function(err, result) { |
| 617 | // Count down | |
| 618 | 0 | numberOfConnections = numberOfConnections - 1; |
| 619 | // Work around the case where the number of connections are 0 | |
| 620 | 0 | if(numberOfConnections <= 0 && typeof callback == 'function') { |
| 621 | 0 | var internalCallback = callback; |
| 622 | 0 | callback = null; |
| 623 | ||
| 624 | // Remove the db from auths | |
| 625 | 0 | self.serverConfig.auth.remove(self.databaseName); |
| 626 | ||
| 627 | // Handle error result | |
| 628 | 0 | utils.handleSingleCommandResultReturn(true, false, internalCallback)(err, result); |
| 629 | } | |
| 630 | }); | |
| 631 | }; | |
| 632 | ||
| 633 | /** | |
| 634 | * Authenticate a user against the server. | |
| 635 | * authMechanism | |
| 636 | * Options | |
| 637 | * - **authMechanism** {String, default:MONGODB-CR}, The authentication mechanism to use, GSSAPI or MONGODB-CR | |
| 638 | * | |
| 639 | * @param {String} username username. | |
| 640 | * @param {String} password password. | |
| 641 | * @param {Object} [options] the options | |
| 642 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from authentication or null if an error occurred. | |
| 643 | * @return {null} | |
| 644 | * @api public | |
| 645 | */ | |
| 646 | 1 | Db.prototype.authenticate = function(username, password, options, callback) { |
| 647 | 0 | var self = this; |
| 648 | ||
| 649 | 0 | if(typeof options == 'function') { |
| 650 | 0 | callback = options; |
| 651 | 0 | options = {}; |
| 652 | } | |
| 653 | ||
| 654 | // Set default mechanism | |
| 655 | 0 | if(!options.authMechanism) { |
| 656 | 0 | options.authMechanism = 'MONGODB-CR'; |
| 657 | 0 | } else if(options.authMechanism != 'GSSAPI' |
| 658 | && options.authMechanism != 'MONGODB-CR' | |
| 659 | && options.authMechanism != 'MONGODB-X509' | |
| 660 | && options.authMechanism != 'PLAIN') { | |
| 661 | 0 | return callback(new Error("only GSSAPI, PLAIN, MONGODB-X509 or MONGODB-CR is supported by authMechanism")); |
| 662 | } | |
| 663 | ||
| 664 | // the default db to authenticate against is 'this' | |
| 665 | // if authententicate is called from a retry context, it may be another one, like admin | |
| 666 | 0 | var authdb = options.authdb ? options.authdb : self.databaseName; |
| 667 | 0 | authdb = options.authSource ? options.authSource : authdb; |
| 668 | ||
| 669 | // Callback | |
| 670 | 0 | var _callback = function(err, result) { |
| 671 | 0 | if(self.listeners("authenticated").length > 9) { |
| 672 | 0 | self.emit("authenticated", err, result); |
| 673 | } | |
| 674 | ||
| 675 | // Return to caller | |
| 676 | 0 | callback(err, result); |
| 677 | } | |
| 678 | ||
| 679 | // If classic auth delegate to auth command | |
| 680 | 0 | if(options.authMechanism == 'MONGODB-CR') { |
| 681 | 0 | mongodb_cr_authenticate(self, username, password, authdb, options, _callback); |
| 682 | 0 | } else if(options.authMechanism == 'PLAIN') { |
| 683 | 0 | mongodb_plain_authenticate(self, username, password, options, _callback); |
| 684 | 0 | } else if(options.authMechanism == 'MONGODB-X509') { |
| 685 | 0 | mongodb_x509_authenticate(self, username, password, options, _callback); |
| 686 | 0 | } else if(options.authMechanism == 'GSSAPI') { |
| 687 | // | |
| 688 | // Kerberos library is not installed, throw and error | |
| 689 | 0 | if(hasKerberos == false) { |
| 690 | 0 | console.log("========================================================================================"); |
| 691 | 0 | console.log("= Please make sure that you install the Kerberos library to use GSSAPI ="); |
| 692 | 0 | console.log("= ="); |
| 693 | 0 | console.log("= npm install -g kerberos ="); |
| 694 | 0 | console.log("= ="); |
| 695 | 0 | console.log("= The Kerberos package is not installed by default for simplicities sake ="); |
| 696 | 0 | console.log("= and needs to be global install ="); |
| 697 | 0 | console.log("========================================================================================"); |
| 698 | 0 | throw new Error("Kerberos library not installed"); |
| 699 | } | |
| 700 | ||
| 701 | 0 | if(process.platform == 'win32') { |
| 702 | 0 | mongodb_sspi_authenticate(self, username, password, authdb, options, _callback); |
| 703 | } else { | |
| 704 | // We have the kerberos library, execute auth process | |
| 705 | 0 | mongodb_gssapi_authenticate(self, username, password, authdb, options, _callback); |
| 706 | } | |
| 707 | } | |
| 708 | }; | |
| 709 | ||
| 710 | /** | |
| 711 | * Add a user to the database. | |
| 712 | * | |
| 713 | * Options | |
| 714 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 715 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 716 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 717 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 718 | * - **customData**, (Object, default:{}) custom data associated with the user (only Mongodb 2.6 or higher) | |
| 719 | * - **roles**, (Array, default:[]) roles associated with the created user (only Mongodb 2.6 or higher) | |
| 720 | * | |
| 721 | * Deprecated Options | |
| 722 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 723 | * | |
| 724 | * @param {String} username username. | |
| 725 | * @param {String} password password. | |
| 726 | * @param {Object} [options] additional options during update. | |
| 727 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from addUser or null if an error occurred. | |
| 728 | * @return {null} | |
| 729 | * @api public | |
| 730 | */ | |
| 731 | 1 | Db.prototype.addUser = function(username, password, options, callback) { |
| 732 | // Checkout a write connection to get the server capabilities | |
| 733 | 0 | var connection = this.serverConfig.checkoutWriter(); |
| 734 | 0 | if(connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasAuthCommands) { |
| 735 | 0 | return _executeAuthCreateUserCommand(this, username, password, options, callback); |
| 736 | } | |
| 737 | ||
| 738 | // Unpack the parameters | |
| 739 | 0 | var self = this; |
| 740 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 741 | 0 | callback = args.pop(); |
| 742 | 0 | options = args.length ? args.shift() || {} : {}; |
| 743 | ||
| 744 | // Get the error options | |
| 745 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 746 | 0 | errorOptions.w = errorOptions.w == null ? 1 : errorOptions.w; |
| 747 | // Use node md5 generator | |
| 748 | 0 | var md5 = crypto.createHash('md5'); |
| 749 | // Generate keys used for authentication | |
| 750 | 0 | md5.update(username + ":mongo:" + password); |
| 751 | 0 | var userPassword = md5.digest('hex'); |
| 752 | // Fetch a user collection | |
| 753 | 0 | var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION); |
| 754 | // Check if we are inserting the first user | |
| 755 | 0 | collection.count({}, function(err, count) { |
| 756 | // We got an error (f.ex not authorized) | |
| 757 | 0 | if(err != null) return callback(err, null); |
| 758 | // Check if the user exists and update i | |
| 759 | 0 | collection.find({user: username}, {dbName: options['dbName']}).toArray(function(err, documents) { |
| 760 | // We got an error (f.ex not authorized) | |
| 761 | 0 | if(err != null) return callback(err, null); |
| 762 | // Add command keys | |
| 763 | 0 | var commandOptions = errorOptions; |
| 764 | 0 | commandOptions.dbName = options['dbName']; |
| 765 | 0 | commandOptions.upsert = true; |
| 766 | ||
| 767 | // We have a user, let's update the password or upsert if not | |
| 768 | 0 | collection.update({user: username},{$set: {user: username, pwd: userPassword}}, commandOptions, function(err, results, full) { |
| 769 | 0 | if(count == 0 && err) { |
| 770 | 0 | callback(null, [{user:username, pwd:userPassword}]); |
| 771 | 0 | } else if(err) { |
| 772 | 0 | callback(err, null) |
| 773 | } else { | |
| 774 | 0 | callback(null, [{user:username, pwd:userPassword}]); |
| 775 | } | |
| 776 | }); | |
| 777 | }); | |
| 778 | }); | |
| 779 | }; | |
| 780 | ||
| 781 | /** | |
| 782 | * @ignore | |
| 783 | */ | |
| 784 | 1 | var _executeAuthCreateUserCommand = function(self, username, password, options, callback) { |
| 785 | // Special case where there is no password ($external users) | |
| 786 | 0 | if(typeof username == 'string' |
| 787 | && password != null && typeof password == 'object') { | |
| 788 | 0 | callback = options; |
| 789 | 0 | options = password; |
| 790 | 0 | password = null; |
| 791 | } | |
| 792 | ||
| 793 | // Unpack all options | |
| 794 | 0 | if(typeof options == 'function') { |
| 795 | 0 | callback = options; |
| 796 | 0 | options = {}; |
| 797 | } | |
| 798 | ||
| 799 | // Error out if we digestPassword set | |
| 800 | 0 | if(options.digestPassword != null) { |
| 801 | 0 | throw utils.toError("The digestPassword option is not supported via add_user. Please use db.command('createUser', ...) instead for this option."); |
| 802 | } | |
| 803 | ||
| 804 | // Get additional values | |
| 805 | 0 | var customData = options.customData != null ? options.customData : {}; |
| 806 | 0 | var roles = Array.isArray(options.roles) ? options.roles : []; |
| 807 | 0 | var maxTimeMS = typeof options.maxTimeMS == 'number' ? options.maxTimeMS : null; |
| 808 | ||
| 809 | // If not roles defined print deprecated message | |
| 810 | 0 | if(roles.length == 0) { |
| 811 | 0 | console.log("Creating a user without roles is deprecated in MongoDB >= 2.6"); |
| 812 | } | |
| 813 | ||
| 814 | // Get the error options | |
| 815 | 0 | var writeConcern = _getWriteConcern(self, options); |
| 816 | 0 | var commandOptions = {writeCommand:true}; |
| 817 | 0 | if(options['dbName']) commandOptions.dbName = options['dbName']; |
| 818 | ||
| 819 | // Add maxTimeMS to options if set | |
| 820 | 0 | if(maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; |
| 821 | ||
| 822 | // Check the db name and add roles if needed | |
| 823 | 0 | if((self.databaseName.toLowerCase() == 'admin' || options.dbName == 'admin') && !Array.isArray(options.roles)) { |
| 824 | 0 | roles = ['root'] |
| 825 | 0 | } else if(!Array.isArray(options.roles)) { |
| 826 | 0 | roles = ['dbOwner'] |
| 827 | } | |
| 828 | ||
| 829 | // Build the command to execute | |
| 830 | 0 | var command = { |
| 831 | createUser: username | |
| 832 | , customData: customData | |
| 833 | , roles: roles | |
| 834 | , digestPassword:false | |
| 835 | , writeConcern: writeConcern | |
| 836 | } | |
| 837 | ||
| 838 | // Use node md5 generator | |
| 839 | 0 | var md5 = crypto.createHash('md5'); |
| 840 | // Generate keys used for authentication | |
| 841 | 0 | md5.update(username + ":mongo:" + password); |
| 842 | 0 | var userPassword = md5.digest('hex'); |
| 843 | ||
| 844 | // No password | |
| 845 | 0 | if(typeof password == 'string') { |
| 846 | 0 | command.pwd = userPassword; |
| 847 | } | |
| 848 | ||
| 849 | // console.log("================================== add user") | |
| 850 | // console.dir(command) | |
| 851 | ||
| 852 | // Execute the command | |
| 853 | 0 | self.command(command, commandOptions, function(err, result) { |
| 854 | 0 | if(err) return callback(err, null); |
| 855 | 0 | callback(!result.ok ? utils.toError("Failed to add user " + username) : null |
| 856 | , result.ok ? [{user: username, pwd: ''}] : null); | |
| 857 | }) | |
| 858 | } | |
| 859 | ||
| 860 | /** | |
| 861 | * Remove a user from a database | |
| 862 | * | |
| 863 | * Options | |
| 864 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 865 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 866 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 867 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 868 | * | |
| 869 | * Deprecated Options | |
| 870 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 871 | * | |
| 872 | * @param {String} username username. | |
| 873 | * @param {Object} [options] additional options during update. | |
| 874 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from removeUser or null if an error occurred. | |
| 875 | * @return {null} | |
| 876 | * @api public | |
| 877 | */ | |
| 878 | 1 | Db.prototype.removeUser = function(username, options, callback) { |
| 879 | // Checkout a write connection to get the server capabilities | |
| 880 | 0 | var connection = this.serverConfig.checkoutWriter(); |
| 881 | 0 | if(connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasAuthCommands) { |
| 882 | 0 | return _executeAuthRemoveUserCommand(this, username, options, callback); |
| 883 | } | |
| 884 | ||
| 885 | // Unpack the parameters | |
| 886 | 0 | var self = this; |
| 887 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 888 | 0 | callback = args.pop(); |
| 889 | 0 | options = args.length ? args.shift() || {} : {}; |
| 890 | ||
| 891 | // Figure out the safe mode settings | |
| 892 | 0 | var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; |
| 893 | // Override with options passed in if applicable | |
| 894 | 0 | safe = options != null && options['safe'] != null ? options['safe'] : safe; |
| 895 | // Ensure it's at least set to safe | |
| 896 | 0 | safe = safe == null ? {w: 1} : safe; |
| 897 | ||
| 898 | // Fetch a user collection | |
| 899 | 0 | var collection = this.collection(DbCommand.SYSTEM_USER_COLLECTION); |
| 900 | 0 | collection.findOne({user: username}, {dbName: options['dbName']}, function(err, user) { |
| 901 | 0 | if(user != null) { |
| 902 | // Add command keys | |
| 903 | 0 | var commandOptions = safe; |
| 904 | 0 | commandOptions.dbName = options['dbName']; |
| 905 | ||
| 906 | 0 | collection.remove({user: username}, commandOptions, function(err, result) { |
| 907 | 0 | callback(err, true); |
| 908 | }); | |
| 909 | } else { | |
| 910 | 0 | callback(err, false); |
| 911 | } | |
| 912 | }); | |
| 913 | }; | |
| 914 | ||
| 915 | 1 | var _executeAuthRemoveUserCommand = function(self, username, options, callback) { |
| 916 | // Unpack all options | |
| 917 | 0 | if(typeof options == 'function') { |
| 918 | 0 | callback = options; |
| 919 | 0 | options = {}; |
| 920 | } | |
| 921 | ||
| 922 | // Get the error options | |
| 923 | 0 | var writeConcern = _getWriteConcern(self, options); |
| 924 | 0 | var commandOptions = {writeCommand:true}; |
| 925 | 0 | if(options['dbName']) commandOptions.dbName = options['dbName']; |
| 926 | ||
| 927 | // Get additional values | |
| 928 | 0 | var maxTimeMS = typeof options.maxTimeMS == 'number' ? options.maxTimeMS : null; |
| 929 | ||
| 930 | // Add maxTimeMS to options if set | |
| 931 | 0 | if(maxTimeMS != null) commandOptions.maxTimeMS = maxTimeMS; |
| 932 | ||
| 933 | // Build the command to execute | |
| 934 | 0 | var command = { |
| 935 | dropUser: username | |
| 936 | , writeConcern: writeConcern | |
| 937 | } | |
| 938 | ||
| 939 | // Execute the command | |
| 940 | 0 | self.command(command, commandOptions, function(err, result) { |
| 941 | 0 | if(err) return callback(err, null); |
| 942 | 0 | callback(null, result.ok ? true : false); |
| 943 | }) | |
| 944 | } | |
| 945 | ||
| 946 | /** | |
| 947 | * Creates a collection on a server pre-allocating space, need to create f.ex capped collections. | |
| 948 | * | |
| 949 | * Options | |
| 950 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 951 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 952 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 953 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 954 | * - **serializeFunctions** {Boolean, default:false}, serialize functions on the document. | |
| 955 | * - **raw** {Boolean, default:false}, perform all operations using raw bson objects. | |
| 956 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 957 | * - **capped** {Boolean, default:false}, create a capped collection. | |
| 958 | * - **size** {Number}, the size of the capped collection in bytes. | |
| 959 | * - **max** {Number}, the maximum number of documents in the capped collection. | |
| 960 | * - **autoIndexId** {Boolean, default:true}, create an index on the _id field of the document, True by default on MongoDB 2.2 or higher off for version < 2.2. | |
| 961 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 962 | * - **strict**, (Boolean, default:false) throws an error if collection already exists | |
| 963 | * | |
| 964 | * Deprecated Options | |
| 965 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 966 | * | |
| 967 | * @param {String} collectionName the collection name we wish to access. | |
| 968 | * @param {Object} [options] returns option results. | |
| 969 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from createCollection or null if an error occurred. | |
| 970 | * @return {null} | |
| 971 | * @api public | |
| 972 | */ | |
| 973 | 1 | Db.prototype.createCollection = function(collectionName, options, callback) { |
| 974 | 0 | var self = this; |
| 975 | 0 | if(typeof options == 'function') { |
| 976 | 0 | callback = options; |
| 977 | 0 | options = {}; |
| 978 | } | |
| 979 | ||
| 980 | // Figure out the safe mode settings | |
| 981 | 0 | var safe = self.safe != null && self.safe == false ? {w: 1} : self.safe; |
| 982 | // Override with options passed in if applicable | |
| 983 | 0 | safe = options != null && options['safe'] != null ? options['safe'] : safe; |
| 984 | // Ensure it's at least set to safe | |
| 985 | 0 | safe = safe == null ? {w: 1} : safe; |
| 986 | ||
| 987 | // Check if we have the name | |
| 988 | 0 | this.collectionNames(collectionName, function(err, collections) { |
| 989 | 0 | if(err != null) return callback(err, null); |
| 990 | ||
| 991 | 0 | var found = false; |
| 992 | 0 | collections.forEach(function(collection) { |
| 993 | 0 | if(collection.name == self.databaseName + "." + collectionName) found = true; |
| 994 | }); | |
| 995 | ||
| 996 | // If the collection exists either throw an exception (if db in safe mode) or return the existing collection | |
| 997 | 0 | if(found && options && options.strict) { |
| 998 | 0 | return callback(new Error("Collection " + collectionName + " already exists. Currently in safe mode."), null); |
| 999 | 0 | } else if(found){ |
| 1000 | 0 | try { |
| 1001 | 0 | var collection = new Collection(self, collectionName, self.pkFactory, options); |
| 1002 | } catch(err) { | |
| 1003 | 0 | return callback(err, null); |
| 1004 | } | |
| 1005 | 0 | return callback(null, collection); |
| 1006 | } | |
| 1007 | ||
| 1008 | // Create a new collection and return it | |
| 1009 | 0 | self._executeQueryCommand(DbCommand.createCreateCollectionCommand(self, collectionName, options) |
| 1010 | , {read:false, safe:safe} | |
| 1011 | , utils.handleSingleCommandResultReturn(null, null, function(err, result) { | |
| 1012 | 0 | if(err) return callback(err, null); |
| 1013 | // Create collection and return | |
| 1014 | 0 | try { |
| 1015 | 0 | return callback(null, new Collection(self, collectionName, self.pkFactory, options)); |
| 1016 | } catch(err) { | |
| 1017 | 0 | return callback(err, null); |
| 1018 | } | |
| 1019 | })); | |
| 1020 | }); | |
| 1021 | }; | |
| 1022 | ||
| 1023 | 1 | var _getReadConcern = function(self, options) { |
| 1024 | 0 | if(options.readPreference) return options.readPreference; |
| 1025 | 0 | if(self.readPreference) return self.readPreference; |
| 1026 | 0 | return 'primary'; |
| 1027 | } | |
| 1028 | ||
| 1029 | /** | |
| 1030 | * Execute a command hash against MongoDB. This lets you acess any commands not available through the api on the server. | |
| 1031 | * | |
| 1032 | * Options | |
| 1033 | * - **maxTimeMS** {Number}, number of miliseconds to wait before aborting the query. | |
| 1034 | * - **ignoreCommandFilter** {Boolean}, overrides the default redirection of certain commands to primary. | |
| 1035 | * - **writeCommand** {Boolean, default: false}, signals this is a write command and to ignore read preferences | |
| 1036 | * - **checkKeys** {Boolean, default: false}, overrides the default not to check the key names for the command | |
| 1037 | * | |
| 1038 | * @param {Object} selector the command hash to send to the server, ex: {ping:1}. | |
| 1039 | * @param {Object} [options] additional options for the command. | |
| 1040 | * @param {Function} callback this will be called after executing this method. The command always return the whole result of the command as the second parameter. | |
| 1041 | * @return {null} | |
| 1042 | * @api public | |
| 1043 | */ | |
| 1044 | 1 | Db.prototype.command = function(selector, options, callback) { |
| 1045 | 0 | if(typeof options == 'function') { |
| 1046 | 0 | callback = options; |
| 1047 | 0 | options = {}; |
| 1048 | } | |
| 1049 | ||
| 1050 | // Ignore command preference (I know what I'm doing) | |
| 1051 | 0 | var ignoreCommandFilter = options.ignoreCommandFilter ? options.ignoreCommandFilter : false; |
| 1052 | // Set read preference if we set one | |
| 1053 | 0 | var readPreference = _getReadConcern(this, options); |
| 1054 | ||
| 1055 | // Ensure only commands who support read Prefrences are exeuted otherwise override and use Primary | |
| 1056 | 0 | if(readPreference != false && ignoreCommandFilter == false) { |
| 1057 | 0 | if(selector['group'] || selector['aggregate'] || selector['collStats'] || selector['dbStats'] |
| 1058 | || selector['count'] || selector['distinct'] || selector['geoNear'] || selector['geoSearch'] | |
| 1059 | || selector['geoWalk'] || selector['text'] | |
| 1060 | || (selector['mapreduce'] && (selector.out == 'inline' || selector.out.inline))) { | |
| 1061 | // Set the read preference | |
| 1062 | 0 | options.readPreference = readPreference; |
| 1063 | } else { | |
| 1064 | 0 | options.readPreference = ReadPreference.PRIMARY; |
| 1065 | } | |
| 1066 | 0 | } else if(readPreference != false) { |
| 1067 | 0 | options.readPreference = readPreference; |
| 1068 | } | |
| 1069 | ||
| 1070 | // Add the maxTimeMS option to the command if specified | |
| 1071 | 0 | if(typeof options.maxTimeMS == 'number') { |
| 1072 | 0 | selector.maxTimeMS = options.maxTimeMS |
| 1073 | } | |
| 1074 | ||
| 1075 | // Command options | |
| 1076 | 0 | var command_options = {}; |
| 1077 | ||
| 1078 | // Do we have an override for checkKeys | |
| 1079 | 0 | if(typeof options['checkKeys'] == 'boolean') command_options['checkKeys'] = options['checkKeys']; |
| 1080 | 0 | command_options['checkKeys'] = typeof options['checkKeys'] == 'boolean' ? options['checkKeys'] : false; |
| 1081 | 0 | if(typeof options['serializeFunctions'] == 'boolean') command_options['serializeFunctions'] = options['serializeFunctions']; |
| 1082 | 0 | if(options['dbName']) command_options['dbName'] = options['dbName']; |
| 1083 | ||
| 1084 | // If we have a write command, remove readPreference as an option | |
| 1085 | 0 | if((options.writeCommand |
| 1086 | || selector['findAndModify'] | |
| 1087 | || selector['insert'] || selector['update'] || selector['delete'] | |
| 1088 | || selector['createUser'] || selector['updateUser'] || selector['removeUser']) | |
| 1089 | && options.readPreference) { | |
| 1090 | 0 | delete options['readPreference']; |
| 1091 | } | |
| 1092 | ||
| 1093 | // Execute a query command | |
| 1094 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, selector, command_options), options, function(err, results) { |
| 1095 | 0 | if(err) return callback(err, null); |
| 1096 | 0 | if(results.documents[0].errmsg) |
| 1097 | 0 | return callback(utils.toError(results.documents[0]), null); |
| 1098 | 0 | callback(null, results.documents[0]); |
| 1099 | }); | |
| 1100 | }; | |
| 1101 | ||
| 1102 | /** | |
| 1103 | * Drop a collection from the database, removing it permanently. New accesses will create a new collection. | |
| 1104 | * | |
| 1105 | * @param {String} collectionName the name of the collection we wish to drop. | |
| 1106 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropCollection or null if an error occurred. | |
| 1107 | * @return {null} | |
| 1108 | * @api public | |
| 1109 | */ | |
| 1110 | 1 | Db.prototype.dropCollection = function(collectionName, callback) { |
| 1111 | 0 | var self = this; |
| 1112 | 0 | callback || (callback = function(){}); |
| 1113 | ||
| 1114 | // Drop the collection | |
| 1115 | 0 | this._executeQueryCommand(DbCommand.createDropCollectionCommand(this, collectionName) |
| 1116 | , utils.handleSingleCommandResultReturn(true, false, callback) | |
| 1117 | ); | |
| 1118 | }; | |
| 1119 | ||
| 1120 | /** | |
| 1121 | * Rename a collection. | |
| 1122 | * | |
| 1123 | * Options | |
| 1124 | * - **dropTarget** {Boolean, default:false}, drop the target name collection if it previously exists. | |
| 1125 | * | |
| 1126 | * @param {String} fromCollection the name of the current collection we wish to rename. | |
| 1127 | * @param {String} toCollection the new name of the collection. | |
| 1128 | * @param {Object} [options] returns option results. | |
| 1129 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from renameCollection or null if an error occurred. | |
| 1130 | * @return {null} | |
| 1131 | * @api public | |
| 1132 | */ | |
| 1133 | 1 | Db.prototype.renameCollection = function(fromCollection, toCollection, options, callback) { |
| 1134 | 0 | var self = this; |
| 1135 | ||
| 1136 | 0 | if(typeof options == 'function') { |
| 1137 | 0 | callback = options; |
| 1138 | 0 | options = {} |
| 1139 | } | |
| 1140 | ||
| 1141 | // Add return new collection | |
| 1142 | 0 | options.new_collection = true; |
| 1143 | ||
| 1144 | // Execute using the collection method | |
| 1145 | 0 | this.collection(fromCollection).rename(toCollection, options, callback); |
| 1146 | }; | |
| 1147 | ||
| 1148 | /** | |
| 1149 | * Return last error message for the given connection, note options can be combined. | |
| 1150 | * | |
| 1151 | * Options | |
| 1152 | * - **fsync** {Boolean, default:false}, option forces the database to fsync all files before returning. | |
| 1153 | * - **j** {Boolean, default:false}, awaits the journal commit before returning, > MongoDB 2.0. | |
| 1154 | * - **w** {Number}, until a write operation has been replicated to N servers. | |
| 1155 | * - **wtimeout** {Number}, number of miliseconds to wait before timing out. | |
| 1156 | * | |
| 1157 | * Connection Options | |
| 1158 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1159 | * | |
| 1160 | * @param {Object} [options] returns option results. | |
| 1161 | * @param {Object} [connectionOptions] returns option results. | |
| 1162 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from lastError or null if an error occurred. | |
| 1163 | * @return {null} | |
| 1164 | * @api public | |
| 1165 | */ | |
| 1166 | 1 | Db.prototype.lastError = function(options, connectionOptions, callback) { |
| 1167 | // Unpack calls | |
| 1168 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1169 | 0 | callback = args.pop(); |
| 1170 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1171 | 0 | connectionOptions = args.length ? args.shift() || {} : {}; |
| 1172 | ||
| 1173 | 0 | this._executeQueryCommand(DbCommand.createGetLastErrorCommand(options, this), connectionOptions, function(err, error) { |
| 1174 | 0 | callback(err, error && error.documents); |
| 1175 | }); | |
| 1176 | }; | |
| 1177 | ||
| 1178 | /** | |
| 1179 | * Legacy method calls. | |
| 1180 | * | |
| 1181 | * @ignore | |
| 1182 | * @api private | |
| 1183 | */ | |
| 1184 | 1 | Db.prototype.error = Db.prototype.lastError; |
| 1185 | 1 | Db.prototype.lastStatus = Db.prototype.lastError; |
| 1186 | ||
| 1187 | /** | |
| 1188 | * Return all errors up to the last time db reset_error_history was called. | |
| 1189 | * | |
| 1190 | * Options | |
| 1191 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1192 | * | |
| 1193 | * @param {Object} [options] returns option results. | |
| 1194 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from previousErrors or null if an error occurred. | |
| 1195 | * @return {null} | |
| 1196 | * @api public | |
| 1197 | */ | |
| 1198 | 1 | Db.prototype.previousErrors = function(options, callback) { |
| 1199 | // Unpack calls | |
| 1200 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1201 | 0 | callback = args.pop(); |
| 1202 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1203 | ||
| 1204 | 0 | this._executeQueryCommand(DbCommand.createGetPreviousErrorsCommand(this), options, function(err, error) { |
| 1205 | 0 | callback(err, error.documents); |
| 1206 | }); | |
| 1207 | }; | |
| 1208 | ||
| 1209 | /** | |
| 1210 | * Runs a command on the database. | |
| 1211 | * @ignore | |
| 1212 | * @api private | |
| 1213 | */ | |
| 1214 | 1 | Db.prototype.executeDbCommand = function(command_hash, options, callback) { |
| 1215 | 0 | if(callback == null) { callback = options; options = {}; } |
| 1216 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, command_hash, options), options, function(err, result) { |
| 1217 | 0 | if(callback) callback(err, result); |
| 1218 | }); | |
| 1219 | }; | |
| 1220 | ||
| 1221 | /** | |
| 1222 | * Runs a command on the database as admin. | |
| 1223 | * @ignore | |
| 1224 | * @api private | |
| 1225 | */ | |
| 1226 | 1 | Db.prototype.executeDbAdminCommand = function(command_hash, options, callback) { |
| 1227 | 0 | if(typeof options == 'function') { |
| 1228 | 0 | callback = options; |
| 1229 | 0 | options = {} |
| 1230 | } | |
| 1231 | ||
| 1232 | 0 | if(options.readPreference) { |
| 1233 | 0 | options.read = options.readPreference; |
| 1234 | } | |
| 1235 | ||
| 1236 | 0 | this._executeQueryCommand(DbCommand.createAdminDbCommand(this, command_hash), options, function(err, result) { |
| 1237 | 0 | if(callback) callback(err, result); |
| 1238 | }); | |
| 1239 | }; | |
| 1240 | ||
| 1241 | /** | |
| 1242 | * Resets the error history of the mongo instance. | |
| 1243 | * | |
| 1244 | * Options | |
| 1245 | * - **connection** {Connection}, fire the getLastError down a specific connection. | |
| 1246 | * | |
| 1247 | * @param {Object} [options] returns option results. | |
| 1248 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from resetErrorHistory or null if an error occurred. | |
| 1249 | * @return {null} | |
| 1250 | * @api public | |
| 1251 | */ | |
| 1252 | 1 | Db.prototype.resetErrorHistory = function(options, callback) { |
| 1253 | // Unpack calls | |
| 1254 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1255 | 0 | callback = args.pop(); |
| 1256 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1257 | ||
| 1258 | 0 | this._executeQueryCommand(DbCommand.createResetErrorHistoryCommand(this), options, function(err, error) { |
| 1259 | 0 | if(callback) callback(err, error && error.documents); |
| 1260 | }); | |
| 1261 | }; | |
| 1262 | ||
| 1263 | /** | |
| 1264 | * Creates an index on the collection. | |
| 1265 | * | |
| 1266 | * Options | |
| 1267 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 1268 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 1269 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 1270 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 1271 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 1272 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 1273 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 1274 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 1275 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 1276 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 1277 | * - **v** {Number}, specify the format version of the indexes. | |
| 1278 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 1279 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 1280 | * | |
| 1281 | * Deprecated Options | |
| 1282 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 1283 | * | |
| 1284 | * | |
| 1285 | * @param {String} collectionName name of the collection to create the index on. | |
| 1286 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 1287 | * @param {Object} [options] additional options during update. | |
| 1288 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from createIndex or null if an error occurred. | |
| 1289 | * @return {null} | |
| 1290 | * @api public | |
| 1291 | */ | |
| 1292 | 1 | Db.prototype.createIndex = function(collectionName, fieldOrSpec, options, callback) { |
| 1293 | 0 | var self = this; |
| 1294 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1295 | 0 | callback = args.pop(); |
| 1296 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1297 | 0 | options = typeof callback === 'function' ? options : callback; |
| 1298 | 0 | options = options == null ? {} : options; |
| 1299 | ||
| 1300 | // Get the error options | |
| 1301 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 1302 | // Create command | |
| 1303 | 0 | var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); |
| 1304 | // Default command options | |
| 1305 | 0 | var commandOptions = {}; |
| 1306 | ||
| 1307 | // If we have error conditions set handle them | |
| 1308 | 0 | if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 1309 | // Insert options | |
| 1310 | 0 | commandOptions['read'] = false; |
| 1311 | // If we have safe set set async to false | |
| 1312 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 1313 | ||
| 1314 | // Set safe option | |
| 1315 | 0 | commandOptions['safe'] = errorOptions; |
| 1316 | // If we have an error option | |
| 1317 | 0 | if(typeof errorOptions == 'object') { |
| 1318 | 0 | var keys = Object.keys(errorOptions); |
| 1319 | 0 | for(var i = 0; i < keys.length; i++) { |
| 1320 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 1321 | } | |
| 1322 | } | |
| 1323 | ||
| 1324 | // Execute insert command | |
| 1325 | 0 | this._executeInsertCommand(command, commandOptions, function(err, result) { |
| 1326 | 0 | if(err != null) return callback(err, null); |
| 1327 | ||
| 1328 | 0 | result = result && result.documents; |
| 1329 | 0 | if (result[0].err) { |
| 1330 | 0 | callback(utils.toError(result[0])); |
| 1331 | } else { | |
| 1332 | 0 | callback(null, command.documents[0].name); |
| 1333 | } | |
| 1334 | }); | |
| 1335 | 0 | } else if(_hasWriteConcern(errorOptions) && callback == null) { |
| 1336 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 1337 | } else { | |
| 1338 | // Execute insert command | |
| 1339 | 0 | var result = this._executeInsertCommand(command, commandOptions); |
| 1340 | // If no callback just return | |
| 1341 | 0 | if(!callback) return; |
| 1342 | // If error return error | |
| 1343 | 0 | if(result instanceof Error) { |
| 1344 | 0 | return callback(result); |
| 1345 | } | |
| 1346 | // Otherwise just return | |
| 1347 | 0 | return callback(null, null); |
| 1348 | } | |
| 1349 | }; | |
| 1350 | ||
| 1351 | /** | |
| 1352 | * Ensures that an index exists, if it does not it creates it | |
| 1353 | * | |
| 1354 | * Options | |
| 1355 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowledgement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 1356 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 1357 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 1358 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 1359 | * - **unique** {Boolean, default:false}, creates an unique index. | |
| 1360 | * - **sparse** {Boolean, default:false}, creates a sparse index. | |
| 1361 | * - **background** {Boolean, default:false}, creates the index in the background, yielding whenever possible. | |
| 1362 | * - **dropDups** {Boolean, default:false}, a unique index cannot be created on a key that has pre-existing duplicate values. If you would like to create the index anyway, keeping the first document the database indexes and deleting all subsequent documents that have duplicate value | |
| 1363 | * - **min** {Number}, for geospatial indexes set the lower bound for the co-ordinates. | |
| 1364 | * - **max** {Number}, for geospatial indexes set the high bound for the co-ordinates. | |
| 1365 | * - **v** {Number}, specify the format version of the indexes. | |
| 1366 | * - **expireAfterSeconds** {Number}, allows you to expire data on indexes applied to a data (MongoDB 2.2 or higher) | |
| 1367 | * - **name** {String}, override the autogenerated index name (useful if the resulting name is larger than 128 bytes) | |
| 1368 | * | |
| 1369 | * Deprecated Options | |
| 1370 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 1371 | * | |
| 1372 | * @param {String} collectionName name of the collection to create the index on. | |
| 1373 | * @param {Object} fieldOrSpec fieldOrSpec that defines the index. | |
| 1374 | * @param {Object} [options] additional options during update. | |
| 1375 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from ensureIndex or null if an error occurred. | |
| 1376 | * @return {null} | |
| 1377 | * @api public | |
| 1378 | */ | |
| 1379 | 1 | Db.prototype.ensureIndex = function(collectionName, fieldOrSpec, options, callback) { |
| 1380 | 0 | var self = this; |
| 1381 | ||
| 1382 | 0 | if (typeof callback === 'undefined' && typeof options === 'function') { |
| 1383 | 0 | callback = options; |
| 1384 | 0 | options = {}; |
| 1385 | } | |
| 1386 | ||
| 1387 | 0 | if (options == null) { |
| 1388 | 0 | options = {}; |
| 1389 | } | |
| 1390 | ||
| 1391 | // Get the error options | |
| 1392 | 0 | var errorOptions = _getWriteConcern(this, options); |
| 1393 | // Make sure we don't try to do a write concern without a callback | |
| 1394 | 0 | if(_hasWriteConcern(errorOptions) && callback == null) |
| 1395 | 0 | throw new Error("Cannot use a writeConcern without a provided callback"); |
| 1396 | // Create command | |
| 1397 | 0 | var command = DbCommand.createCreateIndexCommand(this, collectionName, fieldOrSpec, options); |
| 1398 | 0 | var index_name = command.documents[0].name; |
| 1399 | ||
| 1400 | // Default command options | |
| 1401 | 0 | var commandOptions = {}; |
| 1402 | // Check if the index allready exists | |
| 1403 | 0 | this.indexInformation(collectionName, function(err, collectionInfo) { |
| 1404 | 0 | if(err != null) return callback(err, null); |
| 1405 | ||
| 1406 | 0 | if(!collectionInfo[index_name]) { |
| 1407 | // If we have error conditions set handle them | |
| 1408 | 0 | if(_hasWriteConcern(errorOptions) && typeof callback == 'function') { |
| 1409 | // Insert options | |
| 1410 | 0 | commandOptions['read'] = false; |
| 1411 | // If we have safe set set async to false | |
| 1412 | 0 | if(errorOptions == null) commandOptions['async'] = true; |
| 1413 | ||
| 1414 | // If we have an error option | |
| 1415 | 0 | if(typeof errorOptions == 'object') { |
| 1416 | 0 | var keys = Object.keys(errorOptions); |
| 1417 | 0 | for(var i = 0; i < keys.length; i++) { |
| 1418 | 0 | commandOptions[keys[i]] = errorOptions[keys[i]]; |
| 1419 | } | |
| 1420 | } | |
| 1421 | ||
| 1422 | 0 | if(typeof callback === 'function' |
| 1423 | && commandOptions.w < 1 && !commandOptions.fsync && !commandOptions.journal) { | |
| 1424 | 0 | commandOptions.w = 1; |
| 1425 | } | |
| 1426 | ||
| 1427 | 0 | self._executeInsertCommand(command, commandOptions, function(err, result) { |
| 1428 | // Only callback if we have one specified | |
| 1429 | 0 | if(typeof callback === 'function') { |
| 1430 | 0 | if(err != null) return callback(err, null); |
| 1431 | ||
| 1432 | 0 | result = result && result.documents; |
| 1433 | 0 | if (result[0].err) { |
| 1434 | 0 | callback(utils.toError(result[0])); |
| 1435 | } else { | |
| 1436 | 0 | callback(null, command.documents[0].name); |
| 1437 | } | |
| 1438 | } | |
| 1439 | }); | |
| 1440 | } else { | |
| 1441 | // Execute insert command | |
| 1442 | 0 | var result = self._executeInsertCommand(command, commandOptions); |
| 1443 | // If no callback just return | |
| 1444 | 0 | if(!callback) return; |
| 1445 | // If error return error | |
| 1446 | 0 | if(result instanceof Error) { |
| 1447 | 0 | return callback(result); |
| 1448 | } | |
| 1449 | // Otherwise just return | |
| 1450 | 0 | return callback(null, index_name); |
| 1451 | } | |
| 1452 | } else { | |
| 1453 | 0 | if(typeof callback === 'function') return callback(null, index_name); |
| 1454 | } | |
| 1455 | }); | |
| 1456 | }; | |
| 1457 | ||
| 1458 | /** | |
| 1459 | * Returns the information available on allocated cursors. | |
| 1460 | * | |
| 1461 | * Options | |
| 1462 | * - **readPreference** {String}, the preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 1463 | * | |
| 1464 | * @param {Object} [options] additional options during update. | |
| 1465 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from cursorInfo or null if an error occurred. | |
| 1466 | * @return {null} | |
| 1467 | * @api public | |
| 1468 | */ | |
| 1469 | 1 | Db.prototype.cursorInfo = function(options, callback) { |
| 1470 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1471 | 0 | callback = args.pop(); |
| 1472 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1473 | ||
| 1474 | 0 | this._executeQueryCommand(DbCommand.createDbSlaveOkCommand(this, {'cursorInfo':1}) |
| 1475 | , options | |
| 1476 | , utils.handleSingleCommandResultReturn(null, null, callback)); | |
| 1477 | }; | |
| 1478 | ||
| 1479 | /** | |
| 1480 | * Drop an index on a collection. | |
| 1481 | * | |
| 1482 | * @param {String} collectionName the name of the collection where the command will drop an index. | |
| 1483 | * @param {String} indexName name of the index to drop. | |
| 1484 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropIndex or null if an error occurred. | |
| 1485 | * @return {null} | |
| 1486 | * @api public | |
| 1487 | */ | |
| 1488 | 1 | Db.prototype.dropIndex = function(collectionName, indexName, callback) { |
| 1489 | 0 | this._executeQueryCommand(DbCommand.createDropIndexCommand(this, collectionName, indexName) |
| 1490 | , utils.handleSingleCommandResultReturn(null, null, callback)); | |
| 1491 | }; | |
| 1492 | ||
| 1493 | /** | |
| 1494 | * Reindex all indexes on the collection | |
| 1495 | * Warning: reIndex is a blocking operation (indexes are rebuilt in the foreground) and will be slow for large collections. | |
| 1496 | * | |
| 1497 | * @param {String} collectionName the name of the collection. | |
| 1498 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from reIndex or null if an error occurred. | |
| 1499 | * @api public | |
| 1500 | **/ | |
| 1501 | 1 | Db.prototype.reIndex = function(collectionName, callback) { |
| 1502 | 0 | this._executeQueryCommand(DbCommand.createReIndexCommand(this, collectionName) |
| 1503 | , utils.handleSingleCommandResultReturn(true, false, callback)); | |
| 1504 | }; | |
| 1505 | ||
| 1506 | /** | |
| 1507 | * Retrieves this collections index info. | |
| 1508 | * | |
| 1509 | * Options | |
| 1510 | * - **full** {Boolean, default:false}, returns the full raw index information. | |
| 1511 | * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). | |
| 1512 | * | |
| 1513 | * @param {String} collectionName the name of the collection. | |
| 1514 | * @param {Object} [options] additional options during update. | |
| 1515 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from indexInformation or null if an error occurred. | |
| 1516 | * @return {null} | |
| 1517 | * @api public | |
| 1518 | */ | |
| 1519 | 1 | Db.prototype.indexInformation = function(collectionName, options, callback) { |
| 1520 | 0 | if(typeof callback === 'undefined') { |
| 1521 | 0 | if(typeof options === 'undefined') { |
| 1522 | 0 | callback = collectionName; |
| 1523 | 0 | collectionName = null; |
| 1524 | } else { | |
| 1525 | 0 | callback = options; |
| 1526 | } | |
| 1527 | 0 | options = {}; |
| 1528 | } | |
| 1529 | ||
| 1530 | // If we specified full information | |
| 1531 | 0 | var full = options['full'] == null ? false : options['full']; |
| 1532 | // Build selector for the indexes | |
| 1533 | 0 | var selector = collectionName != null ? {ns: (this.databaseName + "." + collectionName)} : {}; |
| 1534 | ||
| 1535 | // Set read preference if we set one | |
| 1536 | 0 | var readPreference = options['readPreference'] ? options['readPreference'] : ReadPreference.PRIMARY; |
| 1537 | ||
| 1538 | // Iterate through all the fields of the index | |
| 1539 | 0 | this.collection(DbCommand.SYSTEM_INDEX_COLLECTION, function(err, collection) { |
| 1540 | // Perform the find for the collection | |
| 1541 | 0 | collection.find(selector).setReadPreference(readPreference).toArray(function(err, indexes) { |
| 1542 | 0 | if(err != null) return callback(err, null); |
| 1543 | // Contains all the information | |
| 1544 | 0 | var info = {}; |
| 1545 | ||
| 1546 | // if full defined just return all the indexes directly | |
| 1547 | 0 | if(full) return callback(null, indexes); |
| 1548 | ||
| 1549 | // Process all the indexes | |
| 1550 | 0 | for(var i = 0; i < indexes.length; i++) { |
| 1551 | 0 | var index = indexes[i]; |
| 1552 | // Let's unpack the object | |
| 1553 | 0 | info[index.name] = []; |
| 1554 | 0 | for(var name in index.key) { |
| 1555 | 0 | info[index.name].push([name, index.key[name]]); |
| 1556 | } | |
| 1557 | } | |
| 1558 | ||
| 1559 | // Return all the indexes | |
| 1560 | 0 | callback(null, info); |
| 1561 | }); | |
| 1562 | }); | |
| 1563 | }; | |
| 1564 | ||
| 1565 | /** | |
| 1566 | * Drop a database. | |
| 1567 | * | |
| 1568 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from dropDatabase or null if an error occurred. | |
| 1569 | * @return {null} | |
| 1570 | * @api public | |
| 1571 | */ | |
| 1572 | 1 | Db.prototype.dropDatabase = function(callback) { |
| 1573 | 0 | this._executeQueryCommand(DbCommand.createDropDatabaseCommand(this) |
| 1574 | , utils.handleSingleCommandResultReturn(true, false, callback)); | |
| 1575 | } | |
| 1576 | ||
| 1577 | /** | |
| 1578 | * Get all the db statistics. | |
| 1579 | * | |
| 1580 | * Options | |
| 1581 | * - **scale** {Number}, divide the returned sizes by scale value. | |
| 1582 | * - **readPreference** {String}, the preferred read preference ((Server.PRIMARY, Server.PRIMARY_PREFERRED, Server.SECONDARY, Server.SECONDARY_PREFERRED, Server.NEAREST). | |
| 1583 | * | |
| 1584 | * @param {Objects} [options] options for the stats command | |
| 1585 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the results from stats or null if an error occurred. | |
| 1586 | * @return {null} | |
| 1587 | * @api public | |
| 1588 | */ | |
| 1589 | 1 | Db.prototype.stats = function stats(options, callback) { |
| 1590 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 1591 | 0 | callback = args.pop(); |
| 1592 | // Fetch all commands | |
| 1593 | 0 | options = args.length ? args.shift() || {} : {}; |
| 1594 | ||
| 1595 | // Build command object | |
| 1596 | 0 | var commandObject = { |
| 1597 | dbStats:this.collectionName | |
| 1598 | }; | |
| 1599 | ||
| 1600 | // Check if we have the scale value | |
| 1601 | 0 | if(options['scale'] != null) commandObject['scale'] = options['scale']; |
| 1602 | ||
| 1603 | // Execute the command | |
| 1604 | 0 | this.command(commandObject, options, callback); |
| 1605 | } | |
| 1606 | ||
| 1607 | /** | |
| 1608 | * @ignore | |
| 1609 | */ | |
| 1610 | 1 | var __executeQueryCommand = function(self, db_command, options, callback) { |
| 1611 | // Options unpacking | |
| 1612 | 0 | var read = options['read'] != null ? options['read'] : false; |
| 1613 | 0 | read = options['readPreference'] != null && options['read'] == null ? options['readPreference'] : read; |
| 1614 | 0 | var raw = options['raw'] != null ? options['raw'] : self.raw; |
| 1615 | 0 | var onAll = options['onAll'] != null ? options['onAll'] : false; |
| 1616 | 0 | var specifiedConnection = options['connection'] != null ? options['connection'] : null; |
| 1617 | ||
| 1618 | // Correct read preference to default primary if set to false, null or primary | |
| 1619 | 0 | if(!(typeof read == 'object') && read._type == 'ReadPreference') { |
| 1620 | 0 | read = (read == null || read == 'primary' || read == false) ? ReadPreference.PRIMARY : read; |
| 1621 | 0 | if(!ReadPreference.isValid(read)) return callback(new Error("Illegal readPreference mode specified, " + read)); |
| 1622 | 0 | } else if(typeof read == 'object' && read._type == 'ReadPreference') { |
| 1623 | 0 | if(!read.isValid()) return callback(new Error("Illegal readPreference mode specified, " + read.mode)); |
| 1624 | } | |
| 1625 | ||
| 1626 | // If we have a read preference set and we are a mongos pass the read preference on to the mongos instance, | |
| 1627 | 0 | if(self.serverConfig.isMongos() && read != null && read != false) { |
| 1628 | 0 | db_command.setMongosReadPreference(read); |
| 1629 | } | |
| 1630 | ||
| 1631 | // If we got a callback object | |
| 1632 | 0 | if(typeof callback === 'function' && !onAll) { |
| 1633 | // Override connection if we passed in a specific connection | |
| 1634 | 0 | var connection = specifiedConnection != null ? specifiedConnection : null; |
| 1635 | ||
| 1636 | 0 | if(connection instanceof Error) return callback(connection, null); |
| 1637 | ||
| 1638 | // Fetch either a reader or writer dependent on the specified read option if no connection | |
| 1639 | // was passed in | |
| 1640 | 0 | if(connection == null) { |
| 1641 | 0 | connection = self.serverConfig.checkoutReader(read); |
| 1642 | } | |
| 1643 | ||
| 1644 | 0 | if(connection == null) { |
| 1645 | 0 | return callback(new Error("no open connections")); |
| 1646 | 0 | } else if(connection instanceof Error || connection['message'] != null) { |
| 1647 | 0 | return callback(connection); |
| 1648 | } | |
| 1649 | ||
| 1650 | // Exhaust Option | |
| 1651 | 0 | var exhaust = options.exhaust || false; |
| 1652 | ||
| 1653 | // Register the handler in the data structure | |
| 1654 | 0 | self.serverConfig._registerHandler(db_command, raw, connection, exhaust, callback); |
| 1655 | ||
| 1656 | // Write the message out and handle any errors if there are any | |
| 1657 | 0 | connection.write(db_command, function(err) { |
| 1658 | 0 | if(err != null) { |
| 1659 | // Call the handler with an error | |
| 1660 | 0 | if(Array.isArray(db_command)) |
| 1661 | 0 | self.serverConfig._callHandler(db_command[0].getRequestId(), null, err); |
| 1662 | else | |
| 1663 | 0 | self.serverConfig._callHandler(db_command.getRequestId(), null, err); |
| 1664 | } | |
| 1665 | }); | |
| 1666 | 0 | } else if(typeof callback === 'function' && onAll) { |
| 1667 | 0 | var connections = self.serverConfig.allRawConnections(); |
| 1668 | 0 | var numberOfEntries = connections.length; |
| 1669 | // Go through all the connections | |
| 1670 | 0 | for(var i = 0; i < connections.length; i++) { |
| 1671 | // Fetch a connection | |
| 1672 | 0 | var connection = connections[i]; |
| 1673 | ||
| 1674 | // Ensure we have a valid connection | |
| 1675 | 0 | if(connection == null) { |
| 1676 | 0 | return callback(new Error("no open connections")); |
| 1677 | 0 | } else if(connection instanceof Error) { |
| 1678 | 0 | return callback(connection); |
| 1679 | } | |
| 1680 | ||
| 1681 | // Register the handler in the data structure | |
| 1682 | 0 | self.serverConfig._registerHandler(db_command, raw, connection, callback); |
| 1683 | ||
| 1684 | // Write the message out | |
| 1685 | 0 | connection.write(db_command, function(err) { |
| 1686 | // Adjust the number of entries we need to process | |
| 1687 | 0 | numberOfEntries = numberOfEntries - 1; |
| 1688 | // Remove listener | |
| 1689 | 0 | if(err != null) { |
| 1690 | // Clean up listener and return error | |
| 1691 | 0 | self.serverConfig._removeHandler(db_command.getRequestId()); |
| 1692 | } | |
| 1693 | ||
| 1694 | // No more entries to process callback with the error | |
| 1695 | 0 | if(numberOfEntries <= 0) { |
| 1696 | 0 | callback(err); |
| 1697 | } | |
| 1698 | }); | |
| 1699 | ||
| 1700 | // Update the db_command request id | |
| 1701 | 0 | db_command.updateRequestId(); |
| 1702 | } | |
| 1703 | } else { | |
| 1704 | // Fetch either a reader or writer dependent on the specified read option | |
| 1705 | // var connection = read == null || read == 'primary' || read == false ? self.serverConfig.checkoutWriter(true) : self.serverConfig.checkoutReader(read); | |
| 1706 | 0 | var connection = self.serverConfig.checkoutReader(read); |
| 1707 | // Override connection if needed | |
| 1708 | 0 | connection = specifiedConnection != null ? specifiedConnection : connection; |
| 1709 | // Ensure we have a valid connection | |
| 1710 | 0 | if(connection == null || connection instanceof Error || connection['message'] != null) return null; |
| 1711 | // Write the message out | |
| 1712 | 0 | connection.write(db_command, function(err) { |
| 1713 | 0 | if(err != null) { |
| 1714 | // Emit the error | |
| 1715 | 0 | self.emit("error", err); |
| 1716 | } | |
| 1717 | }); | |
| 1718 | } | |
| 1719 | }; | |
| 1720 | ||
| 1721 | /** | |
| 1722 | * Execute db query command (not safe) | |
| 1723 | * @ignore | |
| 1724 | * @api private | |
| 1725 | */ | |
| 1726 | 1 | Db.prototype._executeQueryCommand = function(db_command, options, callback) { |
| 1727 | 0 | var self = this; |
| 1728 | ||
| 1729 | // Unpack the parameters | |
| 1730 | 0 | if (typeof callback === 'undefined') { |
| 1731 | 0 | callback = options; |
| 1732 | 0 | options = {}; |
| 1733 | } | |
| 1734 | ||
| 1735 | // fast fail option used for HA, no retry | |
| 1736 | 0 | var failFast = options['failFast'] != null |
| 1737 | ? options['failFast'] | |
| 1738 | : false; | |
| 1739 | ||
| 1740 | // Check if the user force closed the command | |
| 1741 | 0 | if(this._applicationClosed) { |
| 1742 | 0 | var err = new Error("db closed by application"); |
| 1743 | 0 | if('function' == typeof callback) { |
| 1744 | 0 | return callback(err, null); |
| 1745 | } else { | |
| 1746 | 0 | throw err; |
| 1747 | } | |
| 1748 | } | |
| 1749 | ||
| 1750 | 0 | if(this.serverConfig.isDestroyed()) |
| 1751 | 0 | return callback(new Error("Connection was destroyed by application")); |
| 1752 | ||
| 1753 | // Specific connection | |
| 1754 | 0 | var connection = options.connection; |
| 1755 | // Check if the connection is actually live | |
| 1756 | 0 | if(connection |
| 1757 | 0 | && (!connection.isConnected || !connection.isConnected())) connection = null; |
| 1758 | ||
| 1759 | // Get the configuration | |
| 1760 | 0 | var config = this.serverConfig; |
| 1761 | 0 | var read = options.read; |
| 1762 | // Allow for the usage of the readPreference model | |
| 1763 | 0 | if(read == null) { |
| 1764 | 0 | read = options.readPreference; |
| 1765 | } | |
| 1766 | ||
| 1767 | 0 | if(!connection && !config.canRead(read) && !config.canWrite() && config.isAutoReconnect()) { |
| 1768 | 0 | if(read == ReadPreference.PRIMARY |
| 1769 | || read == ReadPreference.PRIMARY_PREFERRED | |
| 1770 | || (read != null && typeof read == 'object' && read.mode) | |
| 1771 | || read == null) { | |
| 1772 | ||
| 1773 | // Save the command | |
| 1774 | 0 | self.serverConfig._commandsStore.read_from_writer( |
| 1775 | { type: 'query' | |
| 1776 | , db_command: db_command | |
| 1777 | , options: options | |
| 1778 | , callback: callback | |
| 1779 | , db: self | |
| 1780 | , executeQueryCommand: __executeQueryCommand | |
| 1781 | , executeInsertCommand: __executeInsertCommand | |
| 1782 | } | |
| 1783 | ); | |
| 1784 | } else { | |
| 1785 | 0 | self.serverConfig._commandsStore.read( |
| 1786 | { type: 'query' | |
| 1787 | , db_command: db_command | |
| 1788 | , options: options | |
| 1789 | , callback: callback | |
| 1790 | , db: self | |
| 1791 | , executeQueryCommand: __executeQueryCommand | |
| 1792 | , executeInsertCommand: __executeInsertCommand | |
| 1793 | } | |
| 1794 | ); | |
| 1795 | } | |
| 1796 | ||
| 1797 | // If we have blown through the number of items let's | |
| 1798 | 0 | if(!self.serverConfig._commandsStore.validateBufferLimit(self.bufferMaxEntries)) { |
| 1799 | 0 | self.close(); |
| 1800 | } | |
| 1801 | 0 | } else if(!connection && !config.canRead(read) && !config.canWrite() && !config.isAutoReconnect()) { |
| 1802 | 0 | return callback(new Error("no open connections"), null); |
| 1803 | } else { | |
| 1804 | 0 | if(typeof callback == 'function') { |
| 1805 | 0 | __executeQueryCommand(self, db_command, options, function (err, result, conn) { |
| 1806 | 0 | callback(err, result, conn); |
| 1807 | }); | |
| 1808 | } else { | |
| 1809 | 0 | __executeQueryCommand(self, db_command, options); |
| 1810 | } | |
| 1811 | } | |
| 1812 | }; | |
| 1813 | ||
| 1814 | /** | |
| 1815 | * @ignore | |
| 1816 | */ | |
| 1817 | 1 | var __executeInsertCommand = function(self, db_command, options, callback) { |
| 1818 | // Always checkout a writer for this kind of operations | |
| 1819 | 0 | var connection = self.serverConfig.checkoutWriter(); |
| 1820 | // Get safe mode | |
| 1821 | 0 | var safe = options['safe'] != null ? options['safe'] : false; |
| 1822 | 0 | var raw = options['raw'] != null ? options['raw'] : self.raw; |
| 1823 | 0 | var specifiedConnection = options['connection'] != null ? options['connection'] : null; |
| 1824 | // Override connection if needed | |
| 1825 | 0 | connection = specifiedConnection != null ? specifiedConnection : connection; |
| 1826 | ||
| 1827 | // Validate if we can use this server 2.6 wire protocol | |
| 1828 | 0 | if(!connection.isCompatible()) { |
| 1829 | 0 | return callback(utils.toError("driver is incompatible with this server version"), null); |
| 1830 | } | |
| 1831 | ||
| 1832 | // Ensure we have a valid connection | |
| 1833 | 0 | if(typeof callback === 'function') { |
| 1834 | // Ensure we have a valid connection | |
| 1835 | 0 | if(connection == null) { |
| 1836 | 0 | return callback(new Error("no open connections")); |
| 1837 | 0 | } else if(connection instanceof Error) { |
| 1838 | 0 | return callback(connection); |
| 1839 | } | |
| 1840 | ||
| 1841 | 0 | var errorOptions = _getWriteConcern(self, options); |
| 1842 | 0 | if(errorOptions.w > 0 || errorOptions.w == 'majority' || errorOptions.j || errorOptions.journal || errorOptions.fsync) { |
| 1843 | // db command is now an array of commands (original command + lastError) | |
| 1844 | 0 | db_command = [db_command, DbCommand.createGetLastErrorCommand(safe, self)]; |
| 1845 | // Register the handler in the data structure | |
| 1846 | 0 | self.serverConfig._registerHandler(db_command[1], raw, connection, callback); |
| 1847 | } | |
| 1848 | } | |
| 1849 | ||
| 1850 | // If we have no callback and there is no connection | |
| 1851 | 0 | if(connection == null) return null; |
| 1852 | 0 | if(connection instanceof Error && typeof callback == 'function') return callback(connection, null); |
| 1853 | 0 | if(connection instanceof Error) return null; |
| 1854 | 0 | if(connection == null && typeof callback == 'function') return callback(new Error("no primary server found"), null); |
| 1855 | ||
| 1856 | // Write the message out | |
| 1857 | 0 | connection.write(db_command, function(err) { |
| 1858 | // Return the callback if it's not a safe operation and the callback is defined | |
| 1859 | 0 | if(typeof callback === 'function' && (safe == null || safe == false)) { |
| 1860 | // Perform the callback | |
| 1861 | 0 | callback(err, null); |
| 1862 | 0 | } else if(typeof callback === 'function') { |
| 1863 | // Call the handler with an error | |
| 1864 | 0 | self.serverConfig._callHandler(db_command[1].getRequestId(), null, err); |
| 1865 | 0 | } else if(typeof callback == 'function' && safe && safe.w == -1) { |
| 1866 | // Call the handler with no error | |
| 1867 | 0 | self.serverConfig._callHandler(db_command[1].getRequestId(), null, null); |
| 1868 | 0 | } else if(!safe || safe.w == -1) { |
| 1869 | 0 | self.emit("error", err); |
| 1870 | } | |
| 1871 | }); | |
| 1872 | }; | |
| 1873 | ||
| 1874 | /** | |
| 1875 | * Execute an insert Command | |
| 1876 | * @ignore | |
| 1877 | * @api private | |
| 1878 | */ | |
| 1879 | 1 | Db.prototype._executeInsertCommand = function(db_command, options, callback) { |
| 1880 | 0 | var self = this; |
| 1881 | ||
| 1882 | // Unpack the parameters | |
| 1883 | 0 | if(callback == null && typeof options === 'function') { |
| 1884 | 0 | callback = options; |
| 1885 | 0 | options = {}; |
| 1886 | } | |
| 1887 | ||
| 1888 | // Ensure options are not null | |
| 1889 | 0 | options = options == null ? {} : options; |
| 1890 | ||
| 1891 | // Check if the user force closed the command | |
| 1892 | 0 | if(this._applicationClosed) { |
| 1893 | 0 | if(typeof callback == 'function') { |
| 1894 | 0 | return callback(new Error("db closed by application"), null); |
| 1895 | } else { | |
| 1896 | 0 | throw new Error("db closed by application"); |
| 1897 | } | |
| 1898 | } | |
| 1899 | ||
| 1900 | 0 | if(this.serverConfig.isDestroyed()) return callback(new Error("Connection was destroyed by application")); |
| 1901 | ||
| 1902 | // Specific connection | |
| 1903 | 0 | var connection = options.connection; |
| 1904 | // Check if the connection is actually live | |
| 1905 | 0 | if(connection |
| 1906 | 0 | && (!connection.isConnected || !connection.isConnected())) connection = null; |
| 1907 | ||
| 1908 | // Get config | |
| 1909 | 0 | var config = self.serverConfig; |
| 1910 | // Check if we are connected | |
| 1911 | 0 | if(!connection && !config.canWrite() && config.isAutoReconnect()) { |
| 1912 | 0 | self.serverConfig._commandsStore.write( |
| 1913 | { type:'insert' | |
| 1914 | , 'db_command':db_command | |
| 1915 | , 'options':options | |
| 1916 | , 'callback':callback | |
| 1917 | , db: self | |
| 1918 | , executeQueryCommand: __executeQueryCommand | |
| 1919 | , executeInsertCommand: __executeInsertCommand | |
| 1920 | } | |
| 1921 | ); | |
| 1922 | ||
| 1923 | // If we have blown through the number of items let's | |
| 1924 | 0 | if(!self.serverConfig._commandsStore.validateBufferLimit(self.bufferMaxEntries)) { |
| 1925 | 0 | self.close(); |
| 1926 | } | |
| 1927 | 0 | } else if(!connection && !config.canWrite() && !config.isAutoReconnect()) { |
| 1928 | 0 | return callback(new Error("no open connections"), null); |
| 1929 | } else { | |
| 1930 | 0 | __executeInsertCommand(self, db_command, options, callback); |
| 1931 | } | |
| 1932 | }; | |
| 1933 | ||
| 1934 | /** | |
| 1935 | * Update command is the same | |
| 1936 | * @ignore | |
| 1937 | * @api private | |
| 1938 | */ | |
| 1939 | 1 | Db.prototype._executeUpdateCommand = Db.prototype._executeInsertCommand; |
| 1940 | /** | |
| 1941 | * Remove command is the same | |
| 1942 | * @ignore | |
| 1943 | * @api private | |
| 1944 | */ | |
| 1945 | 1 | Db.prototype._executeRemoveCommand = Db.prototype._executeInsertCommand; |
| 1946 | ||
| 1947 | /** | |
| 1948 | * Wrap a Mongo error document into an Error instance. | |
| 1949 | * Deprecated. Use utils.toError instead. | |
| 1950 | * | |
| 1951 | * @ignore | |
| 1952 | * @api private | |
| 1953 | * @deprecated | |
| 1954 | */ | |
| 1955 | 1 | Db.prototype.wrap = utils.toError; |
| 1956 | ||
| 1957 | /** | |
| 1958 | * Default URL | |
| 1959 | * | |
| 1960 | * @classconstant DEFAULT_URL | |
| 1961 | **/ | |
| 1962 | 1 | Db.DEFAULT_URL = 'mongodb://localhost:27017/default'; |
| 1963 | ||
| 1964 | /** | |
| 1965 | * Connect to MongoDB using a url as documented at | |
| 1966 | * | |
| 1967 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 1968 | * | |
| 1969 | * Options | |
| 1970 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 1971 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 1972 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 1973 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 1974 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 1975 | * | |
| 1976 | * @param {String} url connection url for MongoDB. | |
| 1977 | * @param {Object} [options] optional options for insert command | |
| 1978 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occurred, or null otherwise. While the second parameter will contain the db instance or null if an error occurred. | |
| 1979 | * @return {null} | |
| 1980 | * @api public | |
| 1981 | */ | |
| 1982 | 1 | Db.connect = function(url, options, callback) { |
| 1983 | // Ensure correct mapping of the callback | |
| 1984 | 0 | if(typeof options == 'function') { |
| 1985 | 0 | callback = options; |
| 1986 | 0 | options = {}; |
| 1987 | } | |
| 1988 | ||
| 1989 | // Ensure same behavior as previous version w:0 | |
| 1990 | 0 | if(url.indexOf("safe") == -1 |
| 1991 | && url.indexOf("w") == -1 | |
| 1992 | && url.indexOf("journal") == -1 && url.indexOf("j") == -1 | |
| 1993 | 0 | && url.indexOf("fsync") == -1) options.w = 0; |
| 1994 | ||
| 1995 | // Avoid circular require problem | |
| 1996 | 0 | var MongoClient = require('./mongo_client.js').MongoClient; |
| 1997 | // Attempt to connect | |
| 1998 | 0 | MongoClient.connect.call(MongoClient, url, options, callback); |
| 1999 | }; | |
| 2000 | ||
| 2001 | /** | |
| 2002 | * State of the db connection | |
| 2003 | * @ignore | |
| 2004 | */ | |
| 2005 | 1 | Object.defineProperty(Db.prototype, "state", { enumerable: true |
| 2006 | , get: function () { | |
| 2007 | 0 | return this.serverConfig._serverState; |
| 2008 | } | |
| 2009 | }); | |
| 2010 | ||
| 2011 | /** | |
| 2012 | * @ignore | |
| 2013 | */ | |
| 2014 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 2015 | 0 | return errorOptions == true |
| 2016 | || errorOptions.w > 0 | |
| 2017 | || errorOptions.w == 'majority' | |
| 2018 | || errorOptions.j == true | |
| 2019 | || errorOptions.journal == true | |
| 2020 | || errorOptions.fsync == true | |
| 2021 | }; | |
| 2022 | ||
| 2023 | /** | |
| 2024 | * @ignore | |
| 2025 | */ | |
| 2026 | 1 | var _setWriteConcernHash = function(options) { |
| 2027 | 0 | var finalOptions = {}; |
| 2028 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 2029 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 2030 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 2031 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 2032 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 2033 | 0 | return finalOptions; |
| 2034 | }; | |
| 2035 | ||
| 2036 | /** | |
| 2037 | * @ignore | |
| 2038 | */ | |
| 2039 | 1 | var _getWriteConcern = function(self, options, callback) { |
| 2040 | // Final options | |
| 2041 | 0 | var finalOptions = {w:1}; |
| 2042 | // Local options verification | |
| 2043 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 2044 | 0 | finalOptions = _setWriteConcernHash(options); |
| 2045 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 2046 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 2047 | 0 | } else if(typeof options.safe == "boolean") { |
| 2048 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 2049 | 0 | } else if(self.options.w != null || typeof self.options.j == 'boolean' || typeof self.options.journal == 'boolean' || typeof self.options.fsync == 'boolean') { |
| 2050 | 0 | finalOptions = _setWriteConcernHash(self.options); |
| 2051 | 0 | } else if(self.safe.w != null || typeof self.safe.j == 'boolean' || typeof self.safe.journal == 'boolean' || typeof self.safe.fsync == 'boolean') { |
| 2052 | 0 | finalOptions = _setWriteConcernHash(self.safe); |
| 2053 | 0 | } else if(typeof self.safe == "boolean") { |
| 2054 | 0 | finalOptions = {w: (self.safe ? 1 : 0)}; |
| 2055 | } | |
| 2056 | ||
| 2057 | // Ensure we don't have an invalid combination of write concerns | |
| 2058 | 0 | if(finalOptions.w < 1 |
| 2059 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowledgement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 2060 | ||
| 2061 | // Return the options | |
| 2062 | 0 | return finalOptions; |
| 2063 | }; | |
| 2064 | ||
| 2065 | /** | |
| 2066 | * Legacy support | |
| 2067 | * | |
| 2068 | * @ignore | |
| 2069 | * @api private | |
| 2070 | */ | |
| 2071 | 1 | exports.connect = Db.connect; |
| 2072 | 1 | exports.Db = Db; |
| 2073 | ||
| 2074 | /** | |
| 2075 | * Remove all listeners to the db instance. | |
| 2076 | * @ignore | |
| 2077 | * @api private | |
| 2078 | */ | |
| 2079 | 1 | Db.prototype.removeAllEventListeners = function() { |
| 2080 | 0 | this.removeAllListeners("close"); |
| 2081 | 0 | this.removeAllListeners("error"); |
| 2082 | 0 | this.removeAllListeners("timeout"); |
| 2083 | 0 | this.removeAllListeners("parseError"); |
| 2084 | 0 | this.removeAllListeners("poolReady"); |
| 2085 | 0 | this.removeAllListeners("message"); |
| 2086 | }; | |
| 2087 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Binary = require('bson').Binary, |
| 2 | ObjectID = require('bson').ObjectID; | |
| 3 | ||
| 4 | /** | |
| 5 | * Class for representing a single chunk in GridFS. | |
| 6 | * | |
| 7 | * @class | |
| 8 | * | |
| 9 | * @param file {GridStore} The {@link GridStore} object holding this chunk. | |
| 10 | * @param mongoObject {object} The mongo object representation of this chunk. | |
| 11 | * | |
| 12 | * @throws Error when the type of data field for {@link mongoObject} is not | |
| 13 | * supported. Currently supported types for data field are instances of | |
| 14 | * {@link String}, {@link Array}, {@link Binary} and {@link Binary} | |
| 15 | * from the bson module | |
| 16 | * | |
| 17 | * @see Chunk#buildMongoObject | |
| 18 | */ | |
| 19 | 1 | var Chunk = exports.Chunk = function(file, mongoObject, writeConcern) { |
| 20 | 0 | if(!(this instanceof Chunk)) return new Chunk(file, mongoObject); |
| 21 | ||
| 22 | 0 | this.file = file; |
| 23 | 0 | var self = this; |
| 24 | 0 | var mongoObjectFinal = mongoObject == null ? {} : mongoObject; |
| 25 | 0 | this.writeConcern = writeConcern || {w:1}; |
| 26 | 0 | this.objectId = mongoObjectFinal._id == null ? new ObjectID() : mongoObjectFinal._id; |
| 27 | 0 | this.chunkNumber = mongoObjectFinal.n == null ? 0 : mongoObjectFinal.n; |
| 28 | 0 | this.data = new Binary(); |
| 29 | ||
| 30 | 0 | if(mongoObjectFinal.data == null) { |
| 31 | 0 | } else if(typeof mongoObjectFinal.data == "string") { |
| 32 | 0 | var buffer = new Buffer(mongoObjectFinal.data.length); |
| 33 | 0 | buffer.write(mongoObjectFinal.data, 'binary', 0); |
| 34 | 0 | this.data = new Binary(buffer); |
| 35 | 0 | } else if(Array.isArray(mongoObjectFinal.data)) { |
| 36 | 0 | var buffer = new Buffer(mongoObjectFinal.data.length); |
| 37 | 0 | buffer.write(mongoObjectFinal.data.join(''), 'binary', 0); |
| 38 | 0 | this.data = new Binary(buffer); |
| 39 | 0 | } else if(mongoObjectFinal.data instanceof Binary || Object.prototype.toString.call(mongoObjectFinal.data) == "[object Binary]") { |
| 40 | 0 | this.data = mongoObjectFinal.data; |
| 41 | 0 | } else if(Buffer.isBuffer(mongoObjectFinal.data)) { |
| 42 | } else { | |
| 43 | 0 | throw Error("Illegal chunk format"); |
| 44 | } | |
| 45 | // Update position | |
| 46 | 0 | this.internalPosition = 0; |
| 47 | }; | |
| 48 | ||
| 49 | /** | |
| 50 | * Writes a data to this object and advance the read/write head. | |
| 51 | * | |
| 52 | * @param data {string} the data to write | |
| 53 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 54 | * this method. The first parameter will contain null and the second one | |
| 55 | * will contain a reference to this object. | |
| 56 | */ | |
| 57 | 1 | Chunk.prototype.write = function(data, callback) { |
| 58 | 0 | this.data.write(data, this.internalPosition); |
| 59 | 0 | this.internalPosition = this.data.length(); |
| 60 | 0 | if(callback != null) return callback(null, this); |
| 61 | 0 | return this; |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Reads data and advances the read/write head. | |
| 66 | * | |
| 67 | * @param length {number} The length of data to read. | |
| 68 | * | |
| 69 | * @return {string} The data read if the given length will not exceed the end of | |
| 70 | * the chunk. Returns an empty String otherwise. | |
| 71 | */ | |
| 72 | 1 | Chunk.prototype.read = function(length) { |
| 73 | // Default to full read if no index defined | |
| 74 | 0 | length = length == null || length == 0 ? this.length() : length; |
| 75 | ||
| 76 | 0 | if(this.length() - this.internalPosition + 1 >= length) { |
| 77 | 0 | var data = this.data.read(this.internalPosition, length); |
| 78 | 0 | this.internalPosition = this.internalPosition + length; |
| 79 | 0 | return data; |
| 80 | } else { | |
| 81 | 0 | return ''; |
| 82 | } | |
| 83 | }; | |
| 84 | ||
| 85 | 1 | Chunk.prototype.readSlice = function(length) { |
| 86 | 0 | if ((this.length() - this.internalPosition) >= length) { |
| 87 | 0 | var data = null; |
| 88 | 0 | if (this.data.buffer != null) { //Pure BSON |
| 89 | 0 | data = this.data.buffer.slice(this.internalPosition, this.internalPosition + length); |
| 90 | } else { //Native BSON | |
| 91 | 0 | data = new Buffer(length); |
| 92 | 0 | length = this.data.readInto(data, this.internalPosition); |
| 93 | } | |
| 94 | 0 | this.internalPosition = this.internalPosition + length; |
| 95 | 0 | return data; |
| 96 | } else { | |
| 97 | 0 | return null; |
| 98 | } | |
| 99 | }; | |
| 100 | ||
| 101 | /** | |
| 102 | * Checks if the read/write head is at the end. | |
| 103 | * | |
| 104 | * @return {boolean} Whether the read/write head has reached the end of this | |
| 105 | * chunk. | |
| 106 | */ | |
| 107 | 1 | Chunk.prototype.eof = function() { |
| 108 | 0 | return this.internalPosition == this.length() ? true : false; |
| 109 | }; | |
| 110 | ||
| 111 | /** | |
| 112 | * Reads one character from the data of this chunk and advances the read/write | |
| 113 | * head. | |
| 114 | * | |
| 115 | * @return {string} a single character data read if the the read/write head is | |
| 116 | * not at the end of the chunk. Returns an empty String otherwise. | |
| 117 | */ | |
| 118 | 1 | Chunk.prototype.getc = function() { |
| 119 | 0 | return this.read(1); |
| 120 | }; | |
| 121 | ||
| 122 | /** | |
| 123 | * Clears the contents of the data in this chunk and resets the read/write head | |
| 124 | * to the initial position. | |
| 125 | */ | |
| 126 | 1 | Chunk.prototype.rewind = function() { |
| 127 | 0 | this.internalPosition = 0; |
| 128 | 0 | this.data = new Binary(); |
| 129 | }; | |
| 130 | ||
| 131 | /** | |
| 132 | * Saves this chunk to the database. Also overwrites existing entries having the | |
| 133 | * same id as this chunk. | |
| 134 | * | |
| 135 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 136 | * this method. The first parameter will contain null and the second one | |
| 137 | * will contain a reference to this object. | |
| 138 | */ | |
| 139 | 1 | Chunk.prototype.save = function(callback) { |
| 140 | 0 | var self = this; |
| 141 | ||
| 142 | 0 | self.file.chunkCollection(function(err, collection) { |
| 143 | 0 | if(err) return callback(err); |
| 144 | ||
| 145 | 0 | collection.remove({'_id':self.objectId}, self.writeConcern, function(err, result) { |
| 146 | 0 | if(err) return callback(err); |
| 147 | ||
| 148 | 0 | if(self.data.length() > 0) { |
| 149 | 0 | self.buildMongoObject(function(mongoObject) { |
| 150 | 0 | var options = {forceServerObjectId:true}; |
| 151 | 0 | for(var name in self.writeConcern) { |
| 152 | 0 | options[name] = self.writeConcern[name]; |
| 153 | } | |
| 154 | ||
| 155 | 0 | collection.insert(mongoObject, options, function(err, collection) { |
| 156 | 0 | callback(err, self); |
| 157 | }); | |
| 158 | }); | |
| 159 | } else { | |
| 160 | 0 | callback(null, self); |
| 161 | } | |
| 162 | }); | |
| 163 | }); | |
| 164 | }; | |
| 165 | ||
| 166 | /** | |
| 167 | * Creates a mongoDB object representation of this chunk. | |
| 168 | * | |
| 169 | * @param callback {function(Object)} This will be called after executing this | |
| 170 | * method. The object will be passed to the first parameter and will have | |
| 171 | * the structure: | |
| 172 | * | |
| 173 | * <pre><code> | |
| 174 | * { | |
| 175 | * '_id' : , // {number} id for this chunk | |
| 176 | * 'files_id' : , // {number} foreign key to the file collection | |
| 177 | * 'n' : , // {number} chunk number | |
| 178 | * 'data' : , // {bson#Binary} the chunk data itself | |
| 179 | * } | |
| 180 | * </code></pre> | |
| 181 | * | |
| 182 | * @see <a href="http://www.mongodb.org/display/DOCS/GridFS+Specification#GridFSSpecification-{{chunks}}">MongoDB GridFS Chunk Object Structure</a> | |
| 183 | */ | |
| 184 | 1 | Chunk.prototype.buildMongoObject = function(callback) { |
| 185 | 0 | var mongoObject = { |
| 186 | 'files_id': this.file.fileId, | |
| 187 | 'n': this.chunkNumber, | |
| 188 | 'data': this.data}; | |
| 189 | // If we are saving using a specific ObjectId | |
| 190 | 0 | if(this.objectId != null) mongoObject._id = this.objectId; |
| 191 | ||
| 192 | 0 | callback(mongoObject); |
| 193 | }; | |
| 194 | ||
| 195 | /** | |
| 196 | * @return {number} the length of the data | |
| 197 | */ | |
| 198 | 1 | Chunk.prototype.length = function() { |
| 199 | 0 | return this.data.length(); |
| 200 | }; | |
| 201 | ||
| 202 | /** | |
| 203 | * The position of the read/write head | |
| 204 | * @name position | |
| 205 | * @lends Chunk# | |
| 206 | * @field | |
| 207 | */ | |
| 208 | 1 | Object.defineProperty(Chunk.prototype, "position", { enumerable: true |
| 209 | , get: function () { | |
| 210 | 0 | return this.internalPosition; |
| 211 | } | |
| 212 | , set: function(value) { | |
| 213 | 0 | this.internalPosition = value; |
| 214 | } | |
| 215 | }); | |
| 216 | ||
| 217 | /** | |
| 218 | * The default chunk size | |
| 219 | * @constant | |
| 220 | */ | |
| 221 | 1 | Chunk.DEFAULT_CHUNK_SIZE = 1024 * 256; |
| 222 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var GridStore = require('./gridstore').GridStore, |
| 2 | ObjectID = require('bson').ObjectID; | |
| 3 | ||
| 4 | /** | |
| 5 | * A class representation of a simple Grid interface. | |
| 6 | * | |
| 7 | * @class Represents the Grid. | |
| 8 | * @param {Db} db A database instance to interact with. | |
| 9 | * @param {String} [fsName] optional different root collection for GridFS. | |
| 10 | * @return {Grid} | |
| 11 | */ | |
| 12 | 1 | function Grid(db, fsName) { |
| 13 | ||
| 14 | 0 | if(!(this instanceof Grid)) return new Grid(db, fsName); |
| 15 | ||
| 16 | 0 | this.db = db; |
| 17 | 0 | this.fsName = fsName == null ? GridStore.DEFAULT_ROOT_COLLECTION : fsName; |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Puts binary data to the grid | |
| 22 | * | |
| 23 | * Options | |
| 24 | * - **_id** {Any}, unique id for this file | |
| 25 | * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 26 | * - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. | |
| 27 | * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. | |
| 28 | * - **metadata** {Object}, arbitrary data the user wants to store. | |
| 29 | * | |
| 30 | * @param {Buffer} data buffer with Binary Data. | |
| 31 | * @param {Object} [options] the options for the files. | |
| 32 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 33 | * @return {null} | |
| 34 | * @api public | |
| 35 | */ | |
| 36 | 1 | Grid.prototype.put = function(data, options, callback) { |
| 37 | 0 | var self = this; |
| 38 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 39 | 0 | callback = args.pop(); |
| 40 | 0 | options = args.length ? args.shift() : {}; |
| 41 | // If root is not defined add our default one | |
| 42 | 0 | options['root'] = options['root'] == null ? this.fsName : options['root']; |
| 43 | ||
| 44 | // Return if we don't have a buffer object as data | |
| 45 | 0 | if(!(Buffer.isBuffer(data))) return callback(new Error("Data object must be a buffer object"), null); |
| 46 | // Get filename if we are using it | |
| 47 | 0 | var filename = options['filename'] || null; |
| 48 | // Get id if we are using it | |
| 49 | 0 | var id = options['_id'] || null; |
| 50 | // Create gridstore | |
| 51 | 0 | var gridStore = new GridStore(this.db, id, filename, "w", options); |
| 52 | 0 | gridStore.open(function(err, gridStore) { |
| 53 | 0 | if(err) return callback(err, null); |
| 54 | ||
| 55 | 0 | gridStore.write(data, function(err, result) { |
| 56 | 0 | if(err) return callback(err, null); |
| 57 | ||
| 58 | 0 | gridStore.close(function(err, result) { |
| 59 | 0 | if(err) return callback(err, null); |
| 60 | 0 | callback(null, result); |
| 61 | }) | |
| 62 | }) | |
| 63 | }) | |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Get binary data to the grid | |
| 68 | * | |
| 69 | * @param {Any} id for file. | |
| 70 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 71 | * @return {null} | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | 1 | Grid.prototype.get = function(id, callback) { |
| 75 | // Create gridstore | |
| 76 | 0 | var gridStore = new GridStore(this.db, id, null, "r", {root:this.fsName}); |
| 77 | 0 | gridStore.open(function(err, gridStore) { |
| 78 | 0 | if(err) return callback(err, null); |
| 79 | ||
| 80 | // Return the data | |
| 81 | 0 | gridStore.read(function(err, data) { |
| 82 | 0 | return callback(err, data) |
| 83 | }); | |
| 84 | }) | |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Delete file from grid | |
| 89 | * | |
| 90 | * @param {Any} id for file. | |
| 91 | * @param {Function} callback this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 92 | * @return {null} | |
| 93 | * @api public | |
| 94 | */ | |
| 95 | 1 | Grid.prototype.delete = function(id, callback) { |
| 96 | // Create gridstore | |
| 97 | 0 | GridStore.unlink(this.db, id, {root:this.fsName}, function(err, result) { |
| 98 | 0 | if(err) return callback(err, false); |
| 99 | 0 | return callback(null, true); |
| 100 | }); | |
| 101 | } | |
| 102 | ||
| 103 | 1 | exports.Grid = Grid; |
| 104 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * @fileOverview GridFS is a tool for MongoDB to store files to the database. | |
| 3 | * Because of the restrictions of the object size the database can hold, a | |
| 4 | * facility to split a file into several chunks is needed. The {@link GridStore} | |
| 5 | * class offers a simplified api to interact with files while managing the | |
| 6 | * chunks of split files behind the scenes. More information about GridFS can be | |
| 7 | * found <a href="http://www.mongodb.org/display/DOCS/GridFS">here</a>. | |
| 8 | */ | |
| 9 | 1 | var Chunk = require('./chunk').Chunk, |
| 10 | DbCommand = require('../commands/db_command').DbCommand, | |
| 11 | ObjectID = require('bson').ObjectID, | |
| 12 | Buffer = require('buffer').Buffer, | |
| 13 | fs = require('fs'), | |
| 14 | timers = require('timers'), | |
| 15 | util = require('util'), | |
| 16 | inherits = util.inherits, | |
| 17 | ReadStream = require('./readstream').ReadStream, | |
| 18 | Stream = require('stream'); | |
| 19 | ||
| 20 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 21 | 1 | var processor = require('../utils').processor(); |
| 22 | ||
| 23 | 1 | var REFERENCE_BY_FILENAME = 0, |
| 24 | REFERENCE_BY_ID = 1; | |
| 25 | ||
| 26 | /** | |
| 27 | * A class representation of a file stored in GridFS. | |
| 28 | * | |
| 29 | * Modes | |
| 30 | * - **"r"** - read only. This is the default mode. | |
| 31 | * - **"w"** - write in truncate mode. Existing data will be overwriten. | |
| 32 | * - **w+"** - write in edit mode. | |
| 33 | * | |
| 34 | * Options | |
| 35 | * - **root** {String}, root collection to use. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 36 | * - **content_type** {String}, mime type of the file. Defaults to **{GridStore.DEFAULT_CONTENT_TYPE}**. | |
| 37 | * - **chunk_size** {Number}, size for the chunk. Defaults to **{Chunk.DEFAULT_CHUNK_SIZE}**. | |
| 38 | * - **metadata** {Object}, arbitrary data the user wants to store. | |
| 39 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 40 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 41 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 42 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 43 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 44 | * | |
| 45 | * @class Represents the GridStore. | |
| 46 | * @param {Db} db A database instance to interact with. | |
| 47 | * @param {Any} [id] optional unique id for this file | |
| 48 | * @param {String} [filename] optional filename for this file, no unique constrain on the field | |
| 49 | * @param {String} mode set the mode for this file. | |
| 50 | * @param {Object} options optional properties to specify. | |
| 51 | * @return {GridStore} | |
| 52 | */ | |
| 53 | 1 | var GridStore = function GridStore(db, id, filename, mode, options) { |
| 54 | 0 | if(!(this instanceof GridStore)) return new GridStore(db, id, filename, mode, options); |
| 55 | ||
| 56 | 0 | var self = this; |
| 57 | 0 | this.db = db; |
| 58 | ||
| 59 | // Call stream constructor | |
| 60 | 0 | if(typeof Stream == 'function') { |
| 61 | 0 | Stream.call(this); |
| 62 | } else { | |
| 63 | // 0.4.X backward compatibility fix | |
| 64 | 0 | Stream.Stream.call(this); |
| 65 | } | |
| 66 | ||
| 67 | // Handle options | |
| 68 | 0 | if(typeof options === 'undefined') options = {}; |
| 69 | // Handle mode | |
| 70 | 0 | if(typeof mode === 'undefined') { |
| 71 | 0 | mode = filename; |
| 72 | 0 | filename = undefined; |
| 73 | 0 | } else if(typeof mode == 'object') { |
| 74 | 0 | options = mode; |
| 75 | 0 | mode = filename; |
| 76 | 0 | filename = undefined; |
| 77 | } | |
| 78 | ||
| 79 | 0 | if(id instanceof ObjectID) { |
| 80 | 0 | this.referenceBy = REFERENCE_BY_ID; |
| 81 | 0 | this.fileId = id; |
| 82 | 0 | this.filename = filename; |
| 83 | 0 | } else if(typeof filename == 'undefined') { |
| 84 | 0 | this.referenceBy = REFERENCE_BY_FILENAME; |
| 85 | 0 | this.filename = id; |
| 86 | 0 | if (mode.indexOf('w') != null) { |
| 87 | 0 | this.fileId = new ObjectID(); |
| 88 | } | |
| 89 | } else { | |
| 90 | 0 | this.referenceBy = REFERENCE_BY_ID; |
| 91 | 0 | this.fileId = id; |
| 92 | 0 | this.filename = filename; |
| 93 | } | |
| 94 | ||
| 95 | // Set up the rest | |
| 96 | 0 | this.mode = mode == null ? "r" : mode; |
| 97 | 0 | this.options = options == null ? {w:1} : options; |
| 98 | ||
| 99 | // If we have no write concerns set w:1 as default | |
| 100 | 0 | if(this.options.w == null |
| 101 | && this.options.j == null | |
| 102 | 0 | && this.options.fsync == null) this.options.w = 1; |
| 103 | ||
| 104 | // Set the root if overridden | |
| 105 | 0 | this.root = this.options['root'] == null ? exports.GridStore.DEFAULT_ROOT_COLLECTION : this.options['root']; |
| 106 | 0 | this.position = 0; |
| 107 | 0 | this.readPreference = this.options.readPreference || 'primary'; |
| 108 | 0 | this.writeConcern = _getWriteConcern(this, this.options); |
| 109 | // Set default chunk size | |
| 110 | 0 | this.internalChunkSize = this.options['chunkSize'] == null ? Chunk.DEFAULT_CHUNK_SIZE : this.options['chunkSize']; |
| 111 | } | |
| 112 | ||
| 113 | /** | |
| 114 | * Code for the streaming capabilities of the gridstore object | |
| 115 | * Most code from Aaron heckmanns project https://github.com/aheckmann/gridfs-stream | |
| 116 | * Modified to work on the gridstore object itself | |
| 117 | * @ignore | |
| 118 | */ | |
| 119 | 1 | if(typeof Stream == 'function') { |
| 120 | 1 | GridStore.prototype = { __proto__: Stream.prototype } |
| 121 | } else { | |
| 122 | // Node 0.4.X compatibility code | |
| 123 | 0 | GridStore.prototype = { __proto__: Stream.Stream.prototype } |
| 124 | } | |
| 125 | ||
| 126 | // Move pipe to _pipe | |
| 127 | 1 | GridStore.prototype._pipe = GridStore.prototype.pipe; |
| 128 | ||
| 129 | /** | |
| 130 | * Opens the file from the database and initialize this object. Also creates a | |
| 131 | * new one if file does not exist. | |
| 132 | * | |
| 133 | * @param {Function} callback this will be called after executing this method. The first parameter will contain an **{Error}** object and the second parameter will be null if an error occured. Otherwise, the first parameter will be null and the second will contain the reference to this object. | |
| 134 | * @return {null} | |
| 135 | * @api public | |
| 136 | */ | |
| 137 | 1 | GridStore.prototype.open = function(callback) { |
| 138 | 0 | if( this.mode != "w" && this.mode != "w+" && this.mode != "r"){ |
| 139 | 0 | callback(new Error("Illegal mode " + this.mode), null); |
| 140 | 0 | return; |
| 141 | } | |
| 142 | ||
| 143 | 0 | var self = this; |
| 144 | // If we are writing we need to ensure we have the right indexes for md5's | |
| 145 | 0 | if((self.mode == "w" || self.mode == "w+")) { |
| 146 | // Get files collection | |
| 147 | 0 | self.collection(function(err, collection) { |
| 148 | 0 | if(err) return callback(err); |
| 149 | ||
| 150 | // Put index on filename | |
| 151 | 0 | collection.ensureIndex([['filename', 1]], function(err, index) { |
| 152 | 0 | if(err) return callback(err); |
| 153 | ||
| 154 | // Get chunk collection | |
| 155 | 0 | self.chunkCollection(function(err, chunkCollection) { |
| 156 | 0 | if(err) return callback(err); |
| 157 | ||
| 158 | // Ensure index on chunk collection | |
| 159 | 0 | chunkCollection.ensureIndex([['files_id', 1], ['n', 1]], function(err, index) { |
| 160 | 0 | if(err) return callback(err); |
| 161 | 0 | _open(self, callback); |
| 162 | }); | |
| 163 | }); | |
| 164 | }); | |
| 165 | }); | |
| 166 | } else { | |
| 167 | // Open the gridstore | |
| 168 | 0 | _open(self, callback); |
| 169 | } | |
| 170 | }; | |
| 171 | ||
| 172 | /** | |
| 173 | * Hidding the _open function | |
| 174 | * @ignore | |
| 175 | * @api private | |
| 176 | */ | |
| 177 | 1 | var _open = function(self, callback) { |
| 178 | 0 | self.collection(function(err, collection) { |
| 179 | 0 | if(err!==null) { |
| 180 | 0 | callback(new Error("at collection: "+err), null); |
| 181 | 0 | return; |
| 182 | } | |
| 183 | ||
| 184 | // Create the query | |
| 185 | 0 | var query = self.referenceBy == REFERENCE_BY_ID ? {_id:self.fileId} : {filename:self.filename}; |
| 186 | 0 | query = null == self.fileId && this.filename == null ? null : query; |
| 187 | ||
| 188 | // Fetch the chunks | |
| 189 | 0 | if(query != null) { |
| 190 | 0 | collection.find(query, {readPreference:self.readPreference}, function(err, cursor) { |
| 191 | 0 | if(err) return error(err); |
| 192 | ||
| 193 | // Fetch the file | |
| 194 | 0 | cursor.nextObject(function(err, doc) { |
| 195 | 0 | if(err) return error(err); |
| 196 | ||
| 197 | // Check if the collection for the files exists otherwise prepare the new one | |
| 198 | 0 | if(doc != null) { |
| 199 | 0 | self.fileId = doc._id; |
| 200 | 0 | self.filename = doc.filename; |
| 201 | 0 | self.contentType = doc.contentType; |
| 202 | 0 | self.internalChunkSize = doc.chunkSize; |
| 203 | 0 | self.uploadDate = doc.uploadDate; |
| 204 | 0 | self.aliases = doc.aliases; |
| 205 | 0 | self.length = doc.length; |
| 206 | 0 | self.metadata = doc.metadata; |
| 207 | 0 | self.internalMd5 = doc.md5; |
| 208 | 0 | } else if (self.mode != 'r') { |
| 209 | 0 | self.fileId = self.fileId == null ? new ObjectID() : self.fileId; |
| 210 | 0 | self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; |
| 211 | 0 | self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; |
| 212 | 0 | self.length = 0; |
| 213 | } else { | |
| 214 | 0 | self.length = 0; |
| 215 | 0 | var txtId = self.fileId instanceof ObjectID ? self.fileId.toHexString() : self.fileId; |
| 216 | 0 | return error(new Error((self.referenceBy == REFERENCE_BY_ID ? txtId : self.filename) + " does not exist", self)); |
| 217 | } | |
| 218 | ||
| 219 | // Process the mode of the object | |
| 220 | 0 | if(self.mode == "r") { |
| 221 | 0 | nthChunk(self, 0, function(err, chunk) { |
| 222 | 0 | if(err) return error(err); |
| 223 | 0 | self.currentChunk = chunk; |
| 224 | 0 | self.position = 0; |
| 225 | 0 | callback(null, self); |
| 226 | }); | |
| 227 | 0 | } else if(self.mode == "w") { |
| 228 | // Delete any existing chunks | |
| 229 | 0 | deleteChunks(self, function(err, result) { |
| 230 | 0 | if(err) return error(err); |
| 231 | 0 | self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern); |
| 232 | 0 | self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; |
| 233 | 0 | self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; |
| 234 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 235 | 0 | self.position = 0; |
| 236 | 0 | callback(null, self); |
| 237 | }); | |
| 238 | 0 | } else if(self.mode == "w+") { |
| 239 | 0 | nthChunk(self, lastChunkNumber(self), function(err, chunk) { |
| 240 | 0 | if(err) return error(err); |
| 241 | // Set the current chunk | |
| 242 | 0 | self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk; |
| 243 | 0 | self.currentChunk.position = self.currentChunk.data.length(); |
| 244 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 245 | 0 | self.position = self.length; |
| 246 | 0 | callback(null, self); |
| 247 | }); | |
| 248 | } | |
| 249 | }); | |
| 250 | }); | |
| 251 | } else { | |
| 252 | // Write only mode | |
| 253 | 0 | self.fileId = null == self.fileId ? new ObjectID() : self.fileId; |
| 254 | 0 | self.contentType = exports.GridStore.DEFAULT_CONTENT_TYPE; |
| 255 | 0 | self.internalChunkSize = self.internalChunkSize == null ? Chunk.DEFAULT_CHUNK_SIZE : self.internalChunkSize; |
| 256 | 0 | self.length = 0; |
| 257 | ||
| 258 | 0 | self.chunkCollection(function(err, collection2) { |
| 259 | 0 | if(err) return error(err); |
| 260 | ||
| 261 | // No file exists set up write mode | |
| 262 | 0 | if(self.mode == "w") { |
| 263 | // Delete any existing chunks | |
| 264 | 0 | deleteChunks(self, function(err, result) { |
| 265 | 0 | if(err) return error(err); |
| 266 | 0 | self.currentChunk = new Chunk(self, {'n':0}, self.writeConcern); |
| 267 | 0 | self.contentType = self.options['content_type'] == null ? self.contentType : self.options['content_type']; |
| 268 | 0 | self.internalChunkSize = self.options['chunk_size'] == null ? self.internalChunkSize : self.options['chunk_size']; |
| 269 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 270 | 0 | self.position = 0; |
| 271 | 0 | callback(null, self); |
| 272 | }); | |
| 273 | 0 | } else if(self.mode == "w+") { |
| 274 | 0 | nthChunk(self, lastChunkNumber(self), function(err, chunk) { |
| 275 | 0 | if(err) return error(err); |
| 276 | // Set the current chunk | |
| 277 | 0 | self.currentChunk = chunk == null ? new Chunk(self, {'n':0}, self.writeConcern) : chunk; |
| 278 | 0 | self.currentChunk.position = self.currentChunk.data.length(); |
| 279 | 0 | self.metadata = self.options['metadata'] == null ? self.metadata : self.options['metadata']; |
| 280 | 0 | self.position = self.length; |
| 281 | 0 | callback(null, self); |
| 282 | }); | |
| 283 | } | |
| 284 | }); | |
| 285 | } | |
| 286 | }); | |
| 287 | ||
| 288 | // only pass error to callback once | |
| 289 | 0 | function error (err) { |
| 290 | 0 | if(error.err) return; |
| 291 | 0 | callback(error.err = err); |
| 292 | } | |
| 293 | }; | |
| 294 | ||
| 295 | /** | |
| 296 | * Stores a file from the file system to the GridFS database. | |
| 297 | * | |
| 298 | * @param {String|Buffer|FileHandle} file the file to store. | |
| 299 | * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the the second will contain the reference to this object. | |
| 300 | * @return {null} | |
| 301 | * @api public | |
| 302 | */ | |
| 303 | 1 | GridStore.prototype.writeFile = function (file, callback) { |
| 304 | 0 | var self = this; |
| 305 | 0 | if (typeof file === 'string') { |
| 306 | 0 | fs.open(file, 'r', function (err, fd) { |
| 307 | 0 | if(err) return callback(err); |
| 308 | 0 | self.writeFile(fd, callback); |
| 309 | }); | |
| 310 | 0 | return; |
| 311 | } | |
| 312 | ||
| 313 | 0 | self.open(function (err, self) { |
| 314 | 0 | if(err) return callback(err, self); |
| 315 | ||
| 316 | 0 | fs.fstat(file, function (err, stats) { |
| 317 | 0 | if(err) return callback(err, self); |
| 318 | ||
| 319 | 0 | var offset = 0; |
| 320 | 0 | var index = 0; |
| 321 | 0 | var numberOfChunksLeft = Math.min(stats.size / self.chunkSize); |
| 322 | ||
| 323 | // Write a chunk | |
| 324 | 0 | var writeChunk = function() { |
| 325 | 0 | fs.read(file, self.chunkSize, offset, 'binary', function(err, data, bytesRead) { |
| 326 | 0 | if(err) return callback(err, self); |
| 327 | ||
| 328 | 0 | offset = offset + bytesRead; |
| 329 | ||
| 330 | // Create a new chunk for the data | |
| 331 | 0 | var chunk = new Chunk(self, {n:index++}, self.writeConcern); |
| 332 | 0 | chunk.write(data, function(err, chunk) { |
| 333 | 0 | if(err) return callback(err, self); |
| 334 | ||
| 335 | 0 | chunk.save(function(err, result) { |
| 336 | 0 | if(err) return callback(err, self); |
| 337 | ||
| 338 | 0 | self.position = self.position + data.length; |
| 339 | ||
| 340 | // Point to current chunk | |
| 341 | 0 | self.currentChunk = chunk; |
| 342 | ||
| 343 | 0 | if(offset >= stats.size) { |
| 344 | 0 | fs.close(file); |
| 345 | 0 | self.close(function(err, result) { |
| 346 | 0 | if(err) return callback(err, self); |
| 347 | 0 | return callback(null, self); |
| 348 | }); | |
| 349 | } else { | |
| 350 | 0 | return processor(writeChunk); |
| 351 | } | |
| 352 | }); | |
| 353 | }); | |
| 354 | }); | |
| 355 | } | |
| 356 | ||
| 357 | // Process the first write | |
| 358 | 0 | processor(writeChunk); |
| 359 | }); | |
| 360 | }); | |
| 361 | }; | |
| 362 | ||
| 363 | /** | |
| 364 | * Writes some data. This method will work properly only if initialized with mode | |
| 365 | * "w" or "w+". | |
| 366 | * | |
| 367 | * @param string {string} The data to write. | |
| 368 | * @param close {boolean=false} opt_argument Closes this file after writing if | |
| 369 | * true. | |
| 370 | * @param callback {function(*, GridStore)} This will be called after executing | |
| 371 | * this method. The first parameter will contain null and the second one | |
| 372 | * will contain a reference to this object. | |
| 373 | * | |
| 374 | * @ignore | |
| 375 | * @api private | |
| 376 | */ | |
| 377 | 1 | var writeBuffer = function(self, buffer, close, callback) { |
| 378 | 0 | if(typeof close === "function") { callback = close; close = null; } |
| 379 | 0 | var finalClose = (close == null) ? false : close; |
| 380 | ||
| 381 | 0 | if(self.mode[0] != "w") { |
| 382 | 0 | callback(new Error((self.referenceBy == REFERENCE_BY_ID ? self.toHexString() : self.filename) + " not opened for writing"), null); |
| 383 | } else { | |
| 384 | 0 | if(self.currentChunk.position + buffer.length >= self.chunkSize) { |
| 385 | // Write out the current Chunk and then keep writing until we have less data left than a chunkSize left | |
| 386 | // to a new chunk (recursively) | |
| 387 | 0 | var previousChunkNumber = self.currentChunk.chunkNumber; |
| 388 | 0 | var leftOverDataSize = self.chunkSize - self.currentChunk.position; |
| 389 | 0 | var firstChunkData = buffer.slice(0, leftOverDataSize); |
| 390 | 0 | var leftOverData = buffer.slice(leftOverDataSize); |
| 391 | // A list of chunks to write out | |
| 392 | 0 | var chunksToWrite = [self.currentChunk.write(firstChunkData)]; |
| 393 | // If we have more data left than the chunk size let's keep writing new chunks | |
| 394 | 0 | while(leftOverData.length >= self.chunkSize) { |
| 395 | // Create a new chunk and write to it | |
| 396 | 0 | var newChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern); |
| 397 | 0 | var firstChunkData = leftOverData.slice(0, self.chunkSize); |
| 398 | 0 | leftOverData = leftOverData.slice(self.chunkSize); |
| 399 | // Update chunk number | |
| 400 | 0 | previousChunkNumber = previousChunkNumber + 1; |
| 401 | // Write data | |
| 402 | 0 | newChunk.write(firstChunkData); |
| 403 | // Push chunk to save list | |
| 404 | 0 | chunksToWrite.push(newChunk); |
| 405 | } | |
| 406 | ||
| 407 | // Set current chunk with remaining data | |
| 408 | 0 | self.currentChunk = new Chunk(self, {'n': (previousChunkNumber + 1)}, self.writeConcern); |
| 409 | // If we have left over data write it | |
| 410 | 0 | if(leftOverData.length > 0) self.currentChunk.write(leftOverData); |
| 411 | ||
| 412 | // Update the position for the gridstore | |
| 413 | 0 | self.position = self.position + buffer.length; |
| 414 | // Total number of chunks to write | |
| 415 | 0 | var numberOfChunksToWrite = chunksToWrite.length; |
| 416 | // Write out all the chunks and then return | |
| 417 | 0 | for(var i = 0; i < chunksToWrite.length; i++) { |
| 418 | 0 | var chunk = chunksToWrite[i]; |
| 419 | 0 | chunk.save(function(err, result) { |
| 420 | 0 | if(err) return callback(err); |
| 421 | ||
| 422 | 0 | numberOfChunksToWrite = numberOfChunksToWrite - 1; |
| 423 | ||
| 424 | 0 | if(numberOfChunksToWrite <= 0) { |
| 425 | 0 | return callback(null, self); |
| 426 | } | |
| 427 | }) | |
| 428 | } | |
| 429 | } else { | |
| 430 | // Update the position for the gridstore | |
| 431 | 0 | self.position = self.position + buffer.length; |
| 432 | // We have less data than the chunk size just write it and callback | |
| 433 | 0 | self.currentChunk.write(buffer); |
| 434 | 0 | callback(null, self); |
| 435 | } | |
| 436 | } | |
| 437 | }; | |
| 438 | ||
| 439 | /** | |
| 440 | * Creates a mongoDB object representation of this object. | |
| 441 | * | |
| 442 | * @param callback {function(object)} This will be called after executing this | |
| 443 | * method. The object will be passed to the first parameter and will have | |
| 444 | * the structure: | |
| 445 | * | |
| 446 | * <pre><code> | |
| 447 | * { | |
| 448 | * '_id' : , // {number} id for this file | |
| 449 | * 'filename' : , // {string} name for this file | |
| 450 | * 'contentType' : , // {string} mime type for this file | |
| 451 | * 'length' : , // {number} size of this file? | |
| 452 | * 'chunksize' : , // {number} chunk size used by this file | |
| 453 | * 'uploadDate' : , // {Date} | |
| 454 | * 'aliases' : , // {array of string} | |
| 455 | * 'metadata' : , // {string} | |
| 456 | * } | |
| 457 | * </code></pre> | |
| 458 | * | |
| 459 | * @ignore | |
| 460 | * @api private | |
| 461 | */ | |
| 462 | 1 | var buildMongoObject = function(self, callback) { |
| 463 | // Calcuate the length | |
| 464 | 0 | var mongoObject = { |
| 465 | '_id': self.fileId, | |
| 466 | 'filename': self.filename, | |
| 467 | 'contentType': self.contentType, | |
| 468 | 'length': self.position ? self.position : 0, | |
| 469 | 'chunkSize': self.chunkSize, | |
| 470 | 'uploadDate': self.uploadDate, | |
| 471 | 'aliases': self.aliases, | |
| 472 | 'metadata': self.metadata | |
| 473 | }; | |
| 474 | ||
| 475 | 0 | var md5Command = {filemd5:self.fileId, root:self.root}; |
| 476 | 0 | self.db.command(md5Command, function(err, results) { |
| 477 | 0 | mongoObject.md5 = results.md5; |
| 478 | 0 | callback(mongoObject); |
| 479 | }); | |
| 480 | }; | |
| 481 | ||
| 482 | /** | |
| 483 | * Saves this file to the database. This will overwrite the old entry if it | |
| 484 | * already exists. This will work properly only if mode was initialized to | |
| 485 | * "w" or "w+". | |
| 486 | * | |
| 487 | * @param {Function} callback this will be called after executing this method. Passes an **{Error}** object to the first parameter and null to the second if an error occured. Otherwise, passes null to the first and a reference to this object to the second. | |
| 488 | * @return {null} | |
| 489 | * @api public | |
| 490 | */ | |
| 491 | 1 | GridStore.prototype.close = function(callback) { |
| 492 | 0 | var self = this; |
| 493 | ||
| 494 | 0 | if(self.mode[0] == "w") { |
| 495 | 0 | if(self.currentChunk != null && self.currentChunk.position > 0) { |
| 496 | 0 | self.currentChunk.save(function(err, chunk) { |
| 497 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 498 | ||
| 499 | 0 | self.collection(function(err, files) { |
| 500 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 501 | ||
| 502 | // Build the mongo object | |
| 503 | 0 | if(self.uploadDate != null) { |
| 504 | 0 | files.remove({'_id':self.fileId}, {safe:true}, function(err, collection) { |
| 505 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 506 | ||
| 507 | 0 | buildMongoObject(self, function(mongoObject) { |
| 508 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 509 | 0 | if(typeof callback == 'function') |
| 510 | 0 | callback(err, mongoObject); |
| 511 | }); | |
| 512 | }); | |
| 513 | }); | |
| 514 | } else { | |
| 515 | 0 | self.uploadDate = new Date(); |
| 516 | 0 | buildMongoObject(self, function(mongoObject) { |
| 517 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 518 | 0 | if(typeof callback == 'function') |
| 519 | 0 | callback(err, mongoObject); |
| 520 | }); | |
| 521 | }); | |
| 522 | } | |
| 523 | }); | |
| 524 | }); | |
| 525 | } else { | |
| 526 | 0 | self.collection(function(err, files) { |
| 527 | 0 | if(err && typeof callback == 'function') return callback(err); |
| 528 | ||
| 529 | 0 | self.uploadDate = new Date(); |
| 530 | 0 | buildMongoObject(self, function(mongoObject) { |
| 531 | 0 | files.save(mongoObject, self.writeConcern, function(err) { |
| 532 | 0 | if(typeof callback == 'function') |
| 533 | 0 | callback(err, mongoObject); |
| 534 | }); | |
| 535 | }); | |
| 536 | }); | |
| 537 | } | |
| 538 | 0 | } else if(self.mode[0] == "r") { |
| 539 | 0 | if(typeof callback == 'function') |
| 540 | 0 | callback(null, null); |
| 541 | } else { | |
| 542 | 0 | if(typeof callback == 'function') |
| 543 | 0 | callback(new Error("Illegal mode " + self.mode), null); |
| 544 | } | |
| 545 | }; | |
| 546 | ||
| 547 | /** | |
| 548 | * Gets the nth chunk of this file. | |
| 549 | * | |
| 550 | * @param chunkNumber {number} The nth chunk to retrieve. | |
| 551 | * @param callback {function(*, Chunk|object)} This will be called after | |
| 552 | * executing this method. null will be passed to the first parameter while | |
| 553 | * a new {@link Chunk} instance will be passed to the second parameter if | |
| 554 | * the chunk was found or an empty object {} if not. | |
| 555 | * | |
| 556 | * @ignore | |
| 557 | * @api private | |
| 558 | */ | |
| 559 | 1 | var nthChunk = function(self, chunkNumber, callback) { |
| 560 | 0 | self.chunkCollection(function(err, collection) { |
| 561 | 0 | if(err) return callback(err); |
| 562 | ||
| 563 | 0 | collection.find({'files_id':self.fileId, 'n':chunkNumber}, {readPreference: self.readPreference}, function(err, cursor) { |
| 564 | 0 | if(err) return callback(err); |
| 565 | ||
| 566 | 0 | cursor.nextObject(function(err, chunk) { |
| 567 | 0 | if(err) return callback(err); |
| 568 | ||
| 569 | 0 | var finalChunk = chunk == null ? {} : chunk; |
| 570 | 0 | callback(null, new Chunk(self, finalChunk, self.writeConcern)); |
| 571 | }); | |
| 572 | }); | |
| 573 | }); | |
| 574 | }; | |
| 575 | ||
| 576 | /** | |
| 577 | * | |
| 578 | * @ignore | |
| 579 | * @api private | |
| 580 | */ | |
| 581 | 1 | GridStore.prototype._nthChunk = function(chunkNumber, callback) { |
| 582 | 0 | nthChunk(this, chunkNumber, callback); |
| 583 | } | |
| 584 | ||
| 585 | /** | |
| 586 | * @return {Number} The last chunk number of this file. | |
| 587 | * | |
| 588 | * @ignore | |
| 589 | * @api private | |
| 590 | */ | |
| 591 | 1 | var lastChunkNumber = function(self) { |
| 592 | 0 | return Math.floor(self.length/self.chunkSize); |
| 593 | }; | |
| 594 | ||
| 595 | /** | |
| 596 | * Retrieve this file's chunks collection. | |
| 597 | * | |
| 598 | * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. | |
| 599 | * @return {null} | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | 1 | GridStore.prototype.chunkCollection = function(callback) { |
| 603 | 0 | this.db.collection((this.root + ".chunks"), callback); |
| 604 | }; | |
| 605 | ||
| 606 | /** | |
| 607 | * Deletes all the chunks of this file in the database. | |
| 608 | * | |
| 609 | * @param callback {function(*, boolean)} This will be called after this method | |
| 610 | * executes. Passes null to the first and true to the second argument. | |
| 611 | * | |
| 612 | * @ignore | |
| 613 | * @api private | |
| 614 | */ | |
| 615 | 1 | var deleteChunks = function(self, callback) { |
| 616 | 0 | if(self.fileId != null) { |
| 617 | 0 | self.chunkCollection(function(err, collection) { |
| 618 | 0 | if(err) return callback(err, false); |
| 619 | 0 | collection.remove({'files_id':self.fileId}, {safe:true}, function(err, result) { |
| 620 | 0 | if(err) return callback(err, false); |
| 621 | 0 | callback(null, true); |
| 622 | }); | |
| 623 | }); | |
| 624 | } else { | |
| 625 | 0 | callback(null, true); |
| 626 | } | |
| 627 | }; | |
| 628 | ||
| 629 | /** | |
| 630 | * Deletes all the chunks of this file in the database. | |
| 631 | * | |
| 632 | * @param {Function} callback this will be called after this method executes. Passes null to the first and true to the second argument. | |
| 633 | * @return {null} | |
| 634 | * @api public | |
| 635 | */ | |
| 636 | 1 | GridStore.prototype.unlink = function(callback) { |
| 637 | 0 | var self = this; |
| 638 | 0 | deleteChunks(this, function(err) { |
| 639 | 0 | if(err!==null) { |
| 640 | 0 | err.message = "at deleteChunks: " + err.message; |
| 641 | 0 | return callback(err); |
| 642 | } | |
| 643 | ||
| 644 | 0 | self.collection(function(err, collection) { |
| 645 | 0 | if(err!==null) { |
| 646 | 0 | err.message = "at collection: " + err.message; |
| 647 | 0 | return callback(err); |
| 648 | } | |
| 649 | ||
| 650 | 0 | collection.remove({'_id':self.fileId}, {safe:true}, function(err) { |
| 651 | 0 | callback(err, self); |
| 652 | }); | |
| 653 | }); | |
| 654 | }); | |
| 655 | }; | |
| 656 | ||
| 657 | /** | |
| 658 | * Retrieves the file collection associated with this object. | |
| 659 | * | |
| 660 | * @param {Function} callback this will be called after executing this method. An exception object will be passed to the first parameter when an error occured or null otherwise. A new **{Collection}** object will be passed to the second parameter if no error occured. | |
| 661 | * @return {null} | |
| 662 | * @api public | |
| 663 | */ | |
| 664 | 1 | GridStore.prototype.collection = function(callback) { |
| 665 | 0 | this.db.collection(this.root + ".files", callback); |
| 666 | }; | |
| 667 | ||
| 668 | /** | |
| 669 | * Reads the data of this file. | |
| 670 | * | |
| 671 | * @param {String} [separator] the character to be recognized as the newline separator. | |
| 672 | * @param {Function} callback This will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. | |
| 673 | * @return {null} | |
| 674 | * @api public | |
| 675 | */ | |
| 676 | 1 | GridStore.prototype.readlines = function(separator, callback) { |
| 677 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 678 | 0 | callback = args.pop(); |
| 679 | 0 | separator = args.length ? args.shift() : "\n"; |
| 680 | ||
| 681 | 0 | this.read(function(err, data) { |
| 682 | 0 | if(err) return callback(err); |
| 683 | ||
| 684 | 0 | var items = data.toString().split(separator); |
| 685 | 0 | items = items.length > 0 ? items.splice(0, items.length - 1) : []; |
| 686 | 0 | for(var i = 0; i < items.length; i++) { |
| 687 | 0 | items[i] = items[i] + separator; |
| 688 | } | |
| 689 | ||
| 690 | 0 | callback(null, items); |
| 691 | }); | |
| 692 | }; | |
| 693 | ||
| 694 | /** | |
| 695 | * Deletes all the chunks of this file in the database if mode was set to "w" or | |
| 696 | * "w+" and resets the read/write head to the initial position. | |
| 697 | * | |
| 698 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 699 | * @return {null} | |
| 700 | * @api public | |
| 701 | */ | |
| 702 | 1 | GridStore.prototype.rewind = function(callback) { |
| 703 | 0 | var self = this; |
| 704 | ||
| 705 | 0 | if(this.currentChunk.chunkNumber != 0) { |
| 706 | 0 | if(this.mode[0] == "w") { |
| 707 | 0 | deleteChunks(self, function(err, gridStore) { |
| 708 | 0 | if(err) return callback(err); |
| 709 | 0 | self.currentChunk = new Chunk(self, {'n': 0}, self.writeConcern); |
| 710 | 0 | self.position = 0; |
| 711 | 0 | callback(null, self); |
| 712 | }); | |
| 713 | } else { | |
| 714 | 0 | self.currentChunk(0, function(err, chunk) { |
| 715 | 0 | if(err) return callback(err); |
| 716 | 0 | self.currentChunk = chunk; |
| 717 | 0 | self.currentChunk.rewind(); |
| 718 | 0 | self.position = 0; |
| 719 | 0 | callback(null, self); |
| 720 | }); | |
| 721 | } | |
| 722 | } else { | |
| 723 | 0 | self.currentChunk.rewind(); |
| 724 | 0 | self.position = 0; |
| 725 | 0 | callback(null, self); |
| 726 | } | |
| 727 | }; | |
| 728 | ||
| 729 | /** | |
| 730 | * Retrieves the contents of this file and advances the read/write head. Works with Buffers only. | |
| 731 | * | |
| 732 | * There are 3 signatures for this method: | |
| 733 | * | |
| 734 | * (callback) | |
| 735 | * (length, callback) | |
| 736 | * (length, buffer, callback) | |
| 737 | * | |
| 738 | * @param {Number} [length] the number of characters to read. Reads all the characters from the read/write head to the EOF if not specified. | |
| 739 | * @param {String|Buffer} [buffer] a string to hold temporary data. This is used for storing the string data read so far when recursively calling this method. | |
| 740 | * @param {Function} callback this will be called after this method is executed. null will be passed to the first parameter and a string containing the contents of the buffer concatenated with the contents read from this file will be passed to the second. | |
| 741 | * @return {null} | |
| 742 | * @api public | |
| 743 | */ | |
| 744 | 1 | GridStore.prototype.read = function(length, buffer, callback) { |
| 745 | 0 | var self = this; |
| 746 | ||
| 747 | 0 | var args = Array.prototype.slice.call(arguments, 0); |
| 748 | 0 | callback = args.pop(); |
| 749 | 0 | length = args.length ? args.shift() : null; |
| 750 | 0 | buffer = args.length ? args.shift() : null; |
| 751 | ||
| 752 | // The data is a c-terminated string and thus the length - 1 | |
| 753 | 0 | var finalLength = length == null ? self.length - self.position : length; |
| 754 | 0 | var finalBuffer = buffer == null ? new Buffer(finalLength) : buffer; |
| 755 | // Add a index to buffer to keep track of writing position or apply current index | |
| 756 | 0 | finalBuffer._index = buffer != null && buffer._index != null ? buffer._index : 0; |
| 757 | ||
| 758 | 0 | if((self.currentChunk.length() - self.currentChunk.position + finalBuffer._index) >= finalLength) { |
| 759 | 0 | var slice = self.currentChunk.readSlice(finalLength - finalBuffer._index); |
| 760 | // Copy content to final buffer | |
| 761 | 0 | slice.copy(finalBuffer, finalBuffer._index); |
| 762 | // Update internal position | |
| 763 | 0 | self.position = self.position + finalBuffer.length; |
| 764 | // Check if we don't have a file at all | |
| 765 | 0 | if(finalLength == 0 && finalBuffer.length == 0) return callback(new Error("File does not exist"), null); |
| 766 | // Else return data | |
| 767 | 0 | callback(null, finalBuffer); |
| 768 | } else { | |
| 769 | 0 | var slice = self.currentChunk.readSlice(self.currentChunk.length() - self.currentChunk.position); |
| 770 | // Copy content to final buffer | |
| 771 | 0 | slice.copy(finalBuffer, finalBuffer._index); |
| 772 | // Update index position | |
| 773 | 0 | finalBuffer._index += slice.length; |
| 774 | ||
| 775 | // Load next chunk and read more | |
| 776 | 0 | nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 777 | 0 | if(err) return callback(err); |
| 778 | ||
| 779 | 0 | if(chunk.length() > 0) { |
| 780 | 0 | self.currentChunk = chunk; |
| 781 | 0 | self.read(length, finalBuffer, callback); |
| 782 | } else { | |
| 783 | 0 | if (finalBuffer._index > 0) { |
| 784 | 0 | callback(null, finalBuffer) |
| 785 | } else { | |
| 786 | 0 | callback(new Error("no chunks found for file, possibly corrupt"), null); |
| 787 | } | |
| 788 | } | |
| 789 | }); | |
| 790 | } | |
| 791 | } | |
| 792 | ||
| 793 | /** | |
| 794 | * Retrieves the position of the read/write head of this file. | |
| 795 | * | |
| 796 | * @param {Function} callback This gets called after this method terminates. null is passed to the first parameter and the position is passed to the second. | |
| 797 | * @return {null} | |
| 798 | * @api public | |
| 799 | */ | |
| 800 | 1 | GridStore.prototype.tell = function(callback) { |
| 801 | 0 | callback(null, this.position); |
| 802 | }; | |
| 803 | ||
| 804 | /** | |
| 805 | * Moves the read/write head to a new location. | |
| 806 | * | |
| 807 | * There are 3 signatures for this method | |
| 808 | * | |
| 809 | * Seek Location Modes | |
| 810 | * - **GridStore.IO_SEEK_SET**, **(default)** set the position from the start of the file. | |
| 811 | * - **GridStore.IO_SEEK_CUR**, set the position from the current position in the file. | |
| 812 | * - **GridStore.IO_SEEK_END**, set the position from the end of the file. | |
| 813 | * | |
| 814 | * @param {Number} [position] the position to seek to | |
| 815 | * @param {Number} [seekLocation] seek mode. Use one of the Seek Location modes. | |
| 816 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 817 | * @return {null} | |
| 818 | * @api public | |
| 819 | */ | |
| 820 | 1 | GridStore.prototype.seek = function(position, seekLocation, callback) { |
| 821 | 0 | var self = this; |
| 822 | ||
| 823 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 824 | 0 | callback = args.pop(); |
| 825 | 0 | seekLocation = args.length ? args.shift() : null; |
| 826 | ||
| 827 | 0 | var seekLocationFinal = seekLocation == null ? exports.GridStore.IO_SEEK_SET : seekLocation; |
| 828 | 0 | var finalPosition = position; |
| 829 | 0 | var targetPosition = 0; |
| 830 | ||
| 831 | // Calculate the position | |
| 832 | 0 | if(seekLocationFinal == exports.GridStore.IO_SEEK_CUR) { |
| 833 | 0 | targetPosition = self.position + finalPosition; |
| 834 | 0 | } else if(seekLocationFinal == exports.GridStore.IO_SEEK_END) { |
| 835 | 0 | targetPosition = self.length + finalPosition; |
| 836 | } else { | |
| 837 | 0 | targetPosition = finalPosition; |
| 838 | } | |
| 839 | ||
| 840 | // Get the chunk | |
| 841 | 0 | var newChunkNumber = Math.floor(targetPosition/self.chunkSize); |
| 842 | 0 | if(newChunkNumber != self.currentChunk.chunkNumber) { |
| 843 | 0 | var seekChunk = function() { |
| 844 | 0 | nthChunk(self, newChunkNumber, function(err, chunk) { |
| 845 | 0 | self.currentChunk = chunk; |
| 846 | 0 | self.position = targetPosition; |
| 847 | 0 | self.currentChunk.position = (self.position % self.chunkSize); |
| 848 | 0 | callback(err, self); |
| 849 | }); | |
| 850 | }; | |
| 851 | ||
| 852 | 0 | if(self.mode[0] == 'w') { |
| 853 | 0 | self.currentChunk.save(function(err) { |
| 854 | 0 | if(err) return callback(err); |
| 855 | 0 | seekChunk(); |
| 856 | }); | |
| 857 | } else { | |
| 858 | 0 | seekChunk(); |
| 859 | } | |
| 860 | } else { | |
| 861 | 0 | self.position = targetPosition; |
| 862 | 0 | self.currentChunk.position = (self.position % self.chunkSize); |
| 863 | 0 | callback(null, self); |
| 864 | } | |
| 865 | }; | |
| 866 | ||
| 867 | /** | |
| 868 | * Verify if the file is at EOF. | |
| 869 | * | |
| 870 | * @return {Boolean} true if the read/write head is at the end of this file. | |
| 871 | * @api public | |
| 872 | */ | |
| 873 | 1 | GridStore.prototype.eof = function() { |
| 874 | 0 | return this.position == this.length ? true : false; |
| 875 | }; | |
| 876 | ||
| 877 | /** | |
| 878 | * Retrieves a single character from this file. | |
| 879 | * | |
| 880 | * @param {Function} callback this gets called after this method is executed. Passes null to the first parameter and the character read to the second or null to the second if the read/write head is at the end of the file. | |
| 881 | * @return {null} | |
| 882 | * @api public | |
| 883 | */ | |
| 884 | 1 | GridStore.prototype.getc = function(callback) { |
| 885 | 0 | var self = this; |
| 886 | ||
| 887 | 0 | if(self.eof()) { |
| 888 | 0 | callback(null, null); |
| 889 | 0 | } else if(self.currentChunk.eof()) { |
| 890 | 0 | nthChunk(self, self.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 891 | 0 | self.currentChunk = chunk; |
| 892 | 0 | self.position = self.position + 1; |
| 893 | 0 | callback(err, self.currentChunk.getc()); |
| 894 | }); | |
| 895 | } else { | |
| 896 | 0 | self.position = self.position + 1; |
| 897 | 0 | callback(null, self.currentChunk.getc()); |
| 898 | } | |
| 899 | }; | |
| 900 | ||
| 901 | /** | |
| 902 | * Writes a string to the file with a newline character appended at the end if | |
| 903 | * the given string does not have one. | |
| 904 | * | |
| 905 | * @param {String} string the string to write. | |
| 906 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 907 | * @return {null} | |
| 908 | * @api public | |
| 909 | */ | |
| 910 | 1 | GridStore.prototype.puts = function(string, callback) { |
| 911 | 0 | var finalString = string.match(/\n$/) == null ? string + "\n" : string; |
| 912 | 0 | this.write(finalString, callback); |
| 913 | }; | |
| 914 | ||
| 915 | /** | |
| 916 | * Returns read stream based on this GridStore file | |
| 917 | * | |
| 918 | * Events | |
| 919 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 920 | * - **end** {function() {}} the end event triggers when there is no more documents available. | |
| 921 | * - **close** {function() {}} the close event triggers when the stream is closed. | |
| 922 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 923 | * | |
| 924 | * @param {Boolean} autoclose if true current GridStore will be closed when EOF and 'close' event will be fired | |
| 925 | * @return {null} | |
| 926 | * @api public | |
| 927 | */ | |
| 928 | 1 | GridStore.prototype.stream = function(autoclose) { |
| 929 | 0 | return new ReadStream(autoclose, this); |
| 930 | }; | |
| 931 | ||
| 932 | /** | |
| 933 | * The collection to be used for holding the files and chunks collection. | |
| 934 | * | |
| 935 | * @classconstant DEFAULT_ROOT_COLLECTION | |
| 936 | **/ | |
| 937 | 1 | GridStore.DEFAULT_ROOT_COLLECTION = 'fs'; |
| 938 | ||
| 939 | /** | |
| 940 | * Default file mime type | |
| 941 | * | |
| 942 | * @classconstant DEFAULT_CONTENT_TYPE | |
| 943 | **/ | |
| 944 | 1 | GridStore.DEFAULT_CONTENT_TYPE = 'binary/octet-stream'; |
| 945 | ||
| 946 | /** | |
| 947 | * Seek mode where the given length is absolute. | |
| 948 | * | |
| 949 | * @classconstant IO_SEEK_SET | |
| 950 | **/ | |
| 951 | 1 | GridStore.IO_SEEK_SET = 0; |
| 952 | ||
| 953 | /** | |
| 954 | * Seek mode where the given length is an offset to the current read/write head. | |
| 955 | * | |
| 956 | * @classconstant IO_SEEK_CUR | |
| 957 | **/ | |
| 958 | 1 | GridStore.IO_SEEK_CUR = 1; |
| 959 | ||
| 960 | /** | |
| 961 | * Seek mode where the given length is an offset to the end of the file. | |
| 962 | * | |
| 963 | * @classconstant IO_SEEK_END | |
| 964 | **/ | |
| 965 | 1 | GridStore.IO_SEEK_END = 2; |
| 966 | ||
| 967 | /** | |
| 968 | * Checks if a file exists in the database. | |
| 969 | * | |
| 970 | * Options | |
| 971 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 972 | * | |
| 973 | * @param {Db} db the database to query. | |
| 974 | * @param {String} name the name of the file to look for. | |
| 975 | * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 976 | * @param {Function} callback this will be called after this method executes. Passes null to the first and passes true to the second if the file exists and false otherwise. | |
| 977 | * @return {null} | |
| 978 | * @api public | |
| 979 | */ | |
| 980 | 1 | GridStore.exist = function(db, fileIdObject, rootCollection, options, callback) { |
| 981 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 982 | 0 | callback = args.pop(); |
| 983 | 0 | rootCollection = args.length ? args.shift() : null; |
| 984 | 0 | options = args.length ? args.shift() : {}; |
| 985 | ||
| 986 | // Establish read preference | |
| 987 | 0 | var readPreference = options.readPreference || 'primary'; |
| 988 | // Fetch collection | |
| 989 | 0 | var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; |
| 990 | 0 | db.collection(rootCollectionFinal + ".files", function(err, collection) { |
| 991 | 0 | if(err) return callback(err); |
| 992 | ||
| 993 | // Build query | |
| 994 | 0 | var query = (typeof fileIdObject == 'string' || Object.prototype.toString.call(fileIdObject) == '[object RegExp]' ) |
| 995 | ? {'filename':fileIdObject} | |
| 996 | : {'_id':fileIdObject}; // Attempt to locate file | |
| 997 | ||
| 998 | 0 | collection.find(query, {readPreference:readPreference}, function(err, cursor) { |
| 999 | 0 | if(err) return callback(err); |
| 1000 | ||
| 1001 | 0 | cursor.nextObject(function(err, item) { |
| 1002 | 0 | if(err) return callback(err); |
| 1003 | 0 | callback(null, item == null ? false : true); |
| 1004 | }); | |
| 1005 | }); | |
| 1006 | }); | |
| 1007 | }; | |
| 1008 | ||
| 1009 | /** | |
| 1010 | * Gets the list of files stored in the GridFS. | |
| 1011 | * | |
| 1012 | * @param {Db} db the database to query. | |
| 1013 | * @param {String} [rootCollection] the root collection that holds the files and chunks collection. Defaults to **{GridStore.DEFAULT_ROOT_COLLECTION}**. | |
| 1014 | * @param {Function} callback this will be called after this method executes. Passes null to the first and passes an array of strings containing the names of the files. | |
| 1015 | * @return {null} | |
| 1016 | * @api public | |
| 1017 | */ | |
| 1018 | 1 | GridStore.list = function(db, rootCollection, options, callback) { |
| 1019 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 1020 | 0 | callback = args.pop(); |
| 1021 | 0 | rootCollection = args.length ? args.shift() : null; |
| 1022 | 0 | options = args.length ? args.shift() : {}; |
| 1023 | ||
| 1024 | // Ensure we have correct values | |
| 1025 | 0 | if(rootCollection != null && typeof rootCollection == 'object') { |
| 1026 | 0 | options = rootCollection; |
| 1027 | 0 | rootCollection = null; |
| 1028 | } | |
| 1029 | ||
| 1030 | // Establish read preference | |
| 1031 | 0 | var readPreference = options.readPreference || 'primary'; |
| 1032 | // Check if we are returning by id not filename | |
| 1033 | 0 | var byId = options['id'] != null ? options['id'] : false; |
| 1034 | // Fetch item | |
| 1035 | 0 | var rootCollectionFinal = rootCollection != null ? rootCollection : GridStore.DEFAULT_ROOT_COLLECTION; |
| 1036 | 0 | var items = []; |
| 1037 | 0 | db.collection((rootCollectionFinal + ".files"), function(err, collection) { |
| 1038 | 0 | if(err) return callback(err); |
| 1039 | ||
| 1040 | 0 | collection.find({}, {readPreference:readPreference}, function(err, cursor) { |
| 1041 | 0 | if(err) return callback(err); |
| 1042 | ||
| 1043 | 0 | cursor.each(function(err, item) { |
| 1044 | 0 | if(item != null) { |
| 1045 | 0 | items.push(byId ? item._id : item.filename); |
| 1046 | } else { | |
| 1047 | 0 | callback(err, items); |
| 1048 | } | |
| 1049 | }); | |
| 1050 | }); | |
| 1051 | }); | |
| 1052 | }; | |
| 1053 | ||
| 1054 | /** | |
| 1055 | * Reads the contents of a file. | |
| 1056 | * | |
| 1057 | * This method has the following signatures | |
| 1058 | * | |
| 1059 | * (db, name, callback) | |
| 1060 | * (db, name, length, callback) | |
| 1061 | * (db, name, length, offset, callback) | |
| 1062 | * (db, name, length, offset, options, callback) | |
| 1063 | * | |
| 1064 | * @param {Db} db the database to query. | |
| 1065 | * @param {String} name the name of the file. | |
| 1066 | * @param {Number} [length] the size of data to read. | |
| 1067 | * @param {Number} [offset] the offset from the head of the file of which to start reading from. | |
| 1068 | * @param {Object} [options] the options for the file. | |
| 1069 | * @param {Function} callback this will be called after this method executes. A string with an error message will be passed to the first parameter when the length and offset combination exceeds the length of the file while an Error object will be passed if other forms of error occured, otherwise, a string is passed. The second parameter will contain the data read if successful or null if an error occured. | |
| 1070 | * @return {null} | |
| 1071 | * @api public | |
| 1072 | */ | |
| 1073 | 1 | GridStore.read = function(db, name, length, offset, options, callback) { |
| 1074 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1075 | 0 | callback = args.pop(); |
| 1076 | 0 | length = args.length ? args.shift() : null; |
| 1077 | 0 | offset = args.length ? args.shift() : null; |
| 1078 | 0 | options = args.length ? args.shift() : null; |
| 1079 | ||
| 1080 | 0 | new GridStore(db, name, "r", options).open(function(err, gridStore) { |
| 1081 | 0 | if(err) return callback(err); |
| 1082 | // Make sure we are not reading out of bounds | |
| 1083 | 0 | if(offset && offset >= gridStore.length) return callback("offset larger than size of file", null); |
| 1084 | 0 | if(length && length > gridStore.length) return callback("length is larger than the size of the file", null); |
| 1085 | 0 | if(offset && length && (offset + length) > gridStore.length) return callback("offset and length is larger than the size of the file", null); |
| 1086 | ||
| 1087 | 0 | if(offset != null) { |
| 1088 | 0 | gridStore.seek(offset, function(err, gridStore) { |
| 1089 | 0 | if(err) return callback(err); |
| 1090 | 0 | gridStore.read(length, callback); |
| 1091 | }); | |
| 1092 | } else { | |
| 1093 | 0 | gridStore.read(length, callback); |
| 1094 | } | |
| 1095 | }); | |
| 1096 | }; | |
| 1097 | ||
| 1098 | /** | |
| 1099 | * Reads the data of this file. | |
| 1100 | * | |
| 1101 | * @param {Db} db the database to query. | |
| 1102 | * @param {String} name the name of the file. | |
| 1103 | * @param {String} [separator] the character to be recognized as the newline separator. | |
| 1104 | * @param {Object} [options] file options. | |
| 1105 | * @param {Function} callback this will be called after this method is executed. The first parameter will be null and the second parameter will contain an array of strings representing the entire data, each element representing a line including the separator character. | |
| 1106 | * @return {null} | |
| 1107 | * @api public | |
| 1108 | */ | |
| 1109 | 1 | GridStore.readlines = function(db, name, separator, options, callback) { |
| 1110 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1111 | 0 | callback = args.pop(); |
| 1112 | 0 | separator = args.length ? args.shift() : null; |
| 1113 | 0 | options = args.length ? args.shift() : null; |
| 1114 | ||
| 1115 | 0 | var finalSeperator = separator == null ? "\n" : separator; |
| 1116 | 0 | new GridStore(db, name, "r", options).open(function(err, gridStore) { |
| 1117 | 0 | if(err) return callback(err); |
| 1118 | 0 | gridStore.readlines(finalSeperator, callback); |
| 1119 | }); | |
| 1120 | }; | |
| 1121 | ||
| 1122 | /** | |
| 1123 | * Deletes the chunks and metadata information of a file from GridFS. | |
| 1124 | * | |
| 1125 | * @param {Db} db the database to interact with. | |
| 1126 | * @param {String|Array} names the name/names of the files to delete. | |
| 1127 | * @param {Object} [options] the options for the files. | |
| 1128 | * @callback {Function} this will be called after this method is executed. The first parameter will contain an Error object if an error occured or null otherwise. The second parameter will contain a reference to this object. | |
| 1129 | * @return {null} | |
| 1130 | * @api public | |
| 1131 | */ | |
| 1132 | 1 | GridStore.unlink = function(db, names, options, callback) { |
| 1133 | 0 | var self = this; |
| 1134 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 1135 | 0 | callback = args.pop(); |
| 1136 | 0 | options = args.length ? args.shift() : null; |
| 1137 | ||
| 1138 | 0 | if(names.constructor == Array) { |
| 1139 | 0 | var tc = 0; |
| 1140 | 0 | for(var i = 0; i < names.length; i++) { |
| 1141 | 0 | ++tc; |
| 1142 | 0 | self.unlink(db, names[i], options, function(result) { |
| 1143 | 0 | if(--tc == 0) { |
| 1144 | 0 | callback(null, self); |
| 1145 | } | |
| 1146 | }); | |
| 1147 | } | |
| 1148 | } else { | |
| 1149 | 0 | new GridStore(db, names, "w", options).open(function(err, gridStore) { |
| 1150 | 0 | if(err) return callback(err); |
| 1151 | 0 | deleteChunks(gridStore, function(err, result) { |
| 1152 | 0 | if(err) return callback(err); |
| 1153 | 0 | gridStore.collection(function(err, collection) { |
| 1154 | 0 | if(err) return callback(err); |
| 1155 | 0 | collection.remove({'_id':gridStore.fileId}, {safe:true}, function(err, collection) { |
| 1156 | 0 | callback(err, self); |
| 1157 | }); | |
| 1158 | }); | |
| 1159 | }); | |
| 1160 | }); | |
| 1161 | } | |
| 1162 | }; | |
| 1163 | ||
| 1164 | /** | |
| 1165 | * Returns the current chunksize of the file. | |
| 1166 | * | |
| 1167 | * @field chunkSize | |
| 1168 | * @type {Number} | |
| 1169 | * @getter | |
| 1170 | * @setter | |
| 1171 | * @property return number of bytes in the current chunkSize. | |
| 1172 | */ | |
| 1173 | 1 | Object.defineProperty(GridStore.prototype, "chunkSize", { enumerable: true |
| 1174 | , get: function () { | |
| 1175 | 0 | return this.internalChunkSize; |
| 1176 | } | |
| 1177 | , set: function(value) { | |
| 1178 | 0 | if(!(this.mode[0] == "w" && this.position == 0 && this.uploadDate == null)) { |
| 1179 | 0 | this.internalChunkSize = this.internalChunkSize; |
| 1180 | } else { | |
| 1181 | 0 | this.internalChunkSize = value; |
| 1182 | } | |
| 1183 | } | |
| 1184 | }); | |
| 1185 | ||
| 1186 | /** | |
| 1187 | * The md5 checksum for this file. | |
| 1188 | * | |
| 1189 | * @field md5 | |
| 1190 | * @type {Number} | |
| 1191 | * @getter | |
| 1192 | * @setter | |
| 1193 | * @property return this files md5 checksum. | |
| 1194 | */ | |
| 1195 | 1 | Object.defineProperty(GridStore.prototype, "md5", { enumerable: true |
| 1196 | , get: function () { | |
| 1197 | 0 | return this.internalMd5; |
| 1198 | } | |
| 1199 | }); | |
| 1200 | ||
| 1201 | /** | |
| 1202 | * GridStore Streaming methods | |
| 1203 | * Handles the correct return of the writeable stream status | |
| 1204 | * @ignore | |
| 1205 | */ | |
| 1206 | 1 | Object.defineProperty(GridStore.prototype, "writable", { enumerable: true |
| 1207 | , get: function () { | |
| 1208 | 0 | if(this._writeable == null) { |
| 1209 | 0 | this._writeable = this.mode != null && this.mode.indexOf("w") != -1; |
| 1210 | } | |
| 1211 | // Return the _writeable | |
| 1212 | 0 | return this._writeable; |
| 1213 | } | |
| 1214 | , set: function(value) { | |
| 1215 | 0 | this._writeable = value; |
| 1216 | } | |
| 1217 | }); | |
| 1218 | ||
| 1219 | /** | |
| 1220 | * Handles the correct return of the readable stream status | |
| 1221 | * @ignore | |
| 1222 | */ | |
| 1223 | 1 | Object.defineProperty(GridStore.prototype, "readable", { enumerable: true |
| 1224 | , get: function () { | |
| 1225 | 0 | if(this._readable == null) { |
| 1226 | 0 | this._readable = this.mode != null && this.mode.indexOf("r") != -1; |
| 1227 | } | |
| 1228 | 0 | return this._readable; |
| 1229 | } | |
| 1230 | , set: function(value) { | |
| 1231 | 0 | this._readable = value; |
| 1232 | } | |
| 1233 | }); | |
| 1234 | ||
| 1235 | 1 | GridStore.prototype.paused; |
| 1236 | ||
| 1237 | /** | |
| 1238 | * Handles the correct setting of encoding for the stream | |
| 1239 | * @ignore | |
| 1240 | */ | |
| 1241 | 1 | GridStore.prototype.setEncoding = fs.ReadStream.prototype.setEncoding; |
| 1242 | ||
| 1243 | /** | |
| 1244 | * Handles the end events | |
| 1245 | * @ignore | |
| 1246 | */ | |
| 1247 | 1 | GridStore.prototype.end = function end(data) { |
| 1248 | 0 | var self = this; |
| 1249 | // allow queued data to write before closing | |
| 1250 | 0 | if(!this.writable) return; |
| 1251 | 0 | this.writable = false; |
| 1252 | ||
| 1253 | 0 | if(data) { |
| 1254 | 0 | this._q.push(data); |
| 1255 | } | |
| 1256 | ||
| 1257 | 0 | this.on('drain', function () { |
| 1258 | 0 | self.close(function (err) { |
| 1259 | 0 | if (err) return _error(self, err); |
| 1260 | 0 | self.emit('close'); |
| 1261 | }); | |
| 1262 | }); | |
| 1263 | ||
| 1264 | 0 | _flush(self); |
| 1265 | } | |
| 1266 | ||
| 1267 | /** | |
| 1268 | * Handles the normal writes to gridstore | |
| 1269 | * @ignore | |
| 1270 | */ | |
| 1271 | 1 | var _writeNormal = function(self, data, close, callback) { |
| 1272 | // If we have a buffer write it using the writeBuffer method | |
| 1273 | 0 | if(Buffer.isBuffer(data)) { |
| 1274 | 0 | return writeBuffer(self, data, close, callback); |
| 1275 | } else { | |
| 1276 | // Wrap the string in a buffer and write | |
| 1277 | 0 | return writeBuffer(self, new Buffer(data, 'binary'), close, callback); |
| 1278 | } | |
| 1279 | } | |
| 1280 | ||
| 1281 | /** | |
| 1282 | * Writes some data. This method will work properly only if initialized with mode "w" or "w+". | |
| 1283 | * | |
| 1284 | * @param {String|Buffer} data the data to write. | |
| 1285 | * @param {Boolean} [close] closes this file after writing if set to true. | |
| 1286 | * @param {Function} callback this will be called after executing this method. The first parameter will contain null and the second one will contain a reference to this object. | |
| 1287 | * @return {null} | |
| 1288 | * @api public | |
| 1289 | */ | |
| 1290 | 1 | GridStore.prototype.write = function write(data, close, callback) { |
| 1291 | // If it's a normal write delegate the call | |
| 1292 | 0 | if(typeof close == 'function' || typeof callback == 'function') { |
| 1293 | 0 | return _writeNormal(this, data, close, callback); |
| 1294 | } | |
| 1295 | ||
| 1296 | // Otherwise it's a stream write | |
| 1297 | 0 | var self = this; |
| 1298 | 0 | if (!this.writable) { |
| 1299 | 0 | throw new Error('GridWriteStream is not writable'); |
| 1300 | } | |
| 1301 | ||
| 1302 | // queue data until we open. | |
| 1303 | 0 | if (!this._opened) { |
| 1304 | // Set up a queue to save data until gridstore object is ready | |
| 1305 | 0 | this._q = []; |
| 1306 | 0 | _openStream(self); |
| 1307 | 0 | this._q.push(data); |
| 1308 | 0 | return false; |
| 1309 | } | |
| 1310 | ||
| 1311 | // Push data to queue | |
| 1312 | 0 | this._q.push(data); |
| 1313 | 0 | _flush(this); |
| 1314 | // Return write successful | |
| 1315 | 0 | return true; |
| 1316 | } | |
| 1317 | ||
| 1318 | /** | |
| 1319 | * Handles the destroy part of a stream | |
| 1320 | * @ignore | |
| 1321 | */ | |
| 1322 | 1 | GridStore.prototype.destroy = function destroy() { |
| 1323 | // close and do not emit any more events. queued data is not sent. | |
| 1324 | 0 | if(!this.writable) return; |
| 1325 | 0 | this.readable = false; |
| 1326 | 0 | if(this.writable) { |
| 1327 | 0 | this.writable = false; |
| 1328 | 0 | this._q.length = 0; |
| 1329 | 0 | this.emit('close'); |
| 1330 | } | |
| 1331 | } | |
| 1332 | ||
| 1333 | /** | |
| 1334 | * Handles the destroySoon part of a stream | |
| 1335 | * @ignore | |
| 1336 | */ | |
| 1337 | 1 | GridStore.prototype.destroySoon = function destroySoon() { |
| 1338 | // as soon as write queue is drained, destroy. | |
| 1339 | // may call destroy immediately if no data is queued. | |
| 1340 | 0 | if(!this._q.length) { |
| 1341 | 0 | return this.destroy(); |
| 1342 | } | |
| 1343 | 0 | this._destroying = true; |
| 1344 | } | |
| 1345 | ||
| 1346 | /** | |
| 1347 | * Handles the pipe part of the stream | |
| 1348 | * @ignore | |
| 1349 | */ | |
| 1350 | 1 | GridStore.prototype.pipe = function(destination, options) { |
| 1351 | 0 | var self = this; |
| 1352 | // Open the gridstore | |
| 1353 | 0 | this.open(function(err, result) { |
| 1354 | 0 | if(err) _errorRead(self, err); |
| 1355 | 0 | if(!self.readable) return; |
| 1356 | // Set up the pipe | |
| 1357 | 0 | self._pipe(destination, options); |
| 1358 | // Emit the stream is open | |
| 1359 | 0 | self.emit('open'); |
| 1360 | // Read from the stream | |
| 1361 | 0 | _read(self); |
| 1362 | }) | |
| 1363 | } | |
| 1364 | ||
| 1365 | /** | |
| 1366 | * Internal module methods | |
| 1367 | * @ignore | |
| 1368 | */ | |
| 1369 | 1 | var _read = function _read(self) { |
| 1370 | 0 | if (!self.readable || self.paused || self.reading) { |
| 1371 | 0 | return; |
| 1372 | } | |
| 1373 | ||
| 1374 | 0 | self.reading = true; |
| 1375 | 0 | var stream = self._stream = self.stream(); |
| 1376 | 0 | stream.paused = self.paused; |
| 1377 | ||
| 1378 | 0 | stream.on('data', function (data) { |
| 1379 | 0 | if (self._decoder) { |
| 1380 | 0 | var str = self._decoder.write(data); |
| 1381 | 0 | if (str.length) self.emit('data', str); |
| 1382 | } else { | |
| 1383 | 0 | self.emit('data', data); |
| 1384 | } | |
| 1385 | }); | |
| 1386 | ||
| 1387 | 0 | stream.on('end', function (data) { |
| 1388 | 0 | self.emit('end', data); |
| 1389 | }); | |
| 1390 | ||
| 1391 | 0 | stream.on('error', function (data) { |
| 1392 | 0 | _errorRead(self, data); |
| 1393 | }); | |
| 1394 | ||
| 1395 | 0 | stream.on('close', function (data) { |
| 1396 | 0 | self.emit('close', data); |
| 1397 | }); | |
| 1398 | ||
| 1399 | 0 | self.pause = function () { |
| 1400 | // native doesn't always pause. | |
| 1401 | // bypass its pause() method to hack it | |
| 1402 | 0 | self.paused = stream.paused = true; |
| 1403 | } | |
| 1404 | ||
| 1405 | 0 | self.resume = function () { |
| 1406 | 0 | if(!self.paused) return; |
| 1407 | ||
| 1408 | 0 | self.paused = false; |
| 1409 | 0 | stream.resume(); |
| 1410 | 0 | self.readable = stream.readable; |
| 1411 | } | |
| 1412 | ||
| 1413 | 0 | self.destroy = function () { |
| 1414 | 0 | self.readable = false; |
| 1415 | 0 | stream.destroy(); |
| 1416 | } | |
| 1417 | } | |
| 1418 | ||
| 1419 | /** | |
| 1420 | * pause | |
| 1421 | * @ignore | |
| 1422 | */ | |
| 1423 | 1 | GridStore.prototype.pause = function pause () { |
| 1424 | // Overridden when the GridStore opens. | |
| 1425 | 0 | this.paused = true; |
| 1426 | } | |
| 1427 | ||
| 1428 | /** | |
| 1429 | * resume | |
| 1430 | * @ignore | |
| 1431 | */ | |
| 1432 | 1 | GridStore.prototype.resume = function resume () { |
| 1433 | // Overridden when the GridStore opens. | |
| 1434 | 0 | this.paused = false; |
| 1435 | } | |
| 1436 | ||
| 1437 | /** | |
| 1438 | * Internal module methods | |
| 1439 | * @ignore | |
| 1440 | */ | |
| 1441 | 1 | var _flush = function _flush(self, _force) { |
| 1442 | 0 | if (!self._opened) return; |
| 1443 | 0 | if (!_force && self._flushing) return; |
| 1444 | 0 | self._flushing = true; |
| 1445 | ||
| 1446 | // write the entire q to gridfs | |
| 1447 | 0 | if (!self._q.length) { |
| 1448 | 0 | self._flushing = false; |
| 1449 | 0 | self.emit('drain'); |
| 1450 | ||
| 1451 | 0 | if(self._destroying) { |
| 1452 | 0 | self.destroy(); |
| 1453 | } | |
| 1454 | 0 | return; |
| 1455 | } | |
| 1456 | ||
| 1457 | 0 | self.write(self._q.shift(), function (err, store) { |
| 1458 | 0 | if (err) return _error(self, err); |
| 1459 | 0 | self.emit('progress', store.position); |
| 1460 | 0 | _flush(self, true); |
| 1461 | }); | |
| 1462 | } | |
| 1463 | ||
| 1464 | 1 | var _openStream = function _openStream (self) { |
| 1465 | 0 | if(self._opening == true) return; |
| 1466 | 0 | self._opening = true; |
| 1467 | ||
| 1468 | // Open the store | |
| 1469 | 0 | self.open(function (err, gridstore) { |
| 1470 | 0 | if (err) return _error(self, err); |
| 1471 | 0 | self._opened = true; |
| 1472 | 0 | self.emit('open'); |
| 1473 | 0 | _flush(self); |
| 1474 | }); | |
| 1475 | } | |
| 1476 | ||
| 1477 | 1 | var _error = function _error(self, err) { |
| 1478 | 0 | self.destroy(); |
| 1479 | 0 | self.emit('error', err); |
| 1480 | } | |
| 1481 | ||
| 1482 | 1 | var _errorRead = function _errorRead (self, err) { |
| 1483 | 0 | self.readable = false; |
| 1484 | 0 | self.emit('error', err); |
| 1485 | } | |
| 1486 | ||
| 1487 | /** | |
| 1488 | * @ignore | |
| 1489 | */ | |
| 1490 | 1 | var _hasWriteConcern = function(errorOptions) { |
| 1491 | 0 | return errorOptions == true |
| 1492 | || errorOptions.w > 0 | |
| 1493 | || errorOptions.w == 'majority' | |
| 1494 | || errorOptions.j == true | |
| 1495 | || errorOptions.journal == true | |
| 1496 | || errorOptions.fsync == true | |
| 1497 | } | |
| 1498 | ||
| 1499 | /** | |
| 1500 | * @ignore | |
| 1501 | */ | |
| 1502 | 1 | var _setWriteConcernHash = function(options) { |
| 1503 | 0 | var finalOptions = {}; |
| 1504 | 0 | if(options.w != null) finalOptions.w = options.w; |
| 1505 | 0 | if(options.journal == true) finalOptions.j = options.journal; |
| 1506 | 0 | if(options.j == true) finalOptions.j = options.j; |
| 1507 | 0 | if(options.fsync == true) finalOptions.fsync = options.fsync; |
| 1508 | 0 | if(options.wtimeout != null) finalOptions.wtimeout = options.wtimeout; |
| 1509 | 0 | return finalOptions; |
| 1510 | } | |
| 1511 | ||
| 1512 | /** | |
| 1513 | * @ignore | |
| 1514 | */ | |
| 1515 | 1 | var _getWriteConcern = function(self, options, callback) { |
| 1516 | // Final options | |
| 1517 | 0 | var finalOptions = {w:1}; |
| 1518 | 0 | options = options || {}; |
| 1519 | // Local options verification | |
| 1520 | 0 | if(options.w != null || typeof options.j == 'boolean' || typeof options.journal == 'boolean' || typeof options.fsync == 'boolean') { |
| 1521 | 0 | finalOptions = _setWriteConcernHash(options); |
| 1522 | 0 | } else if(typeof options.safe == "boolean") { |
| 1523 | 0 | finalOptions = {w: (options.safe ? 1 : 0)}; |
| 1524 | 0 | } else if(options.safe != null && typeof options.safe == 'object') { |
| 1525 | 0 | finalOptions = _setWriteConcernHash(options.safe); |
| 1526 | 0 | } else if(self.db.safe.w != null || typeof self.db.safe.j == 'boolean' || typeof self.db.safe.journal == 'boolean' || typeof self.db.safe.fsync == 'boolean') { |
| 1527 | 0 | finalOptions = _setWriteConcernHash(self.db.safe); |
| 1528 | 0 | } else if(self.db.options.w != null || typeof self.db.options.j == 'boolean' || typeof self.db.options.journal == 'boolean' || typeof self.db.options.fsync == 'boolean') { |
| 1529 | 0 | finalOptions = _setWriteConcernHash(self.db.options); |
| 1530 | 0 | } else if(typeof self.db.safe == "boolean") { |
| 1531 | 0 | finalOptions = {w: (self.db.safe ? 1 : 0)}; |
| 1532 | } | |
| 1533 | ||
| 1534 | // Ensure we don't have an invalid combination of write concerns | |
| 1535 | 0 | if(finalOptions.w < 1 |
| 1536 | 0 | && (finalOptions.journal == true || finalOptions.j == true || finalOptions.fsync == true)) throw new Error("No acknowlegement using w < 1 cannot be combined with journal:true or fsync:true"); |
| 1537 | ||
| 1538 | // Return the options | |
| 1539 | 0 | return finalOptions; |
| 1540 | } | |
| 1541 | ||
| 1542 | /** | |
| 1543 | * @ignore | |
| 1544 | * @api private | |
| 1545 | */ | |
| 1546 | 1 | exports.GridStore = GridStore; |
| 1547 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Stream = require('stream').Stream, |
| 2 | timers = require('timers'), | |
| 3 | util = require('util'); | |
| 4 | ||
| 5 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 6 | 1 | var processor = require('../utils').processor(); |
| 7 | ||
| 8 | /** | |
| 9 | * ReadStream | |
| 10 | * | |
| 11 | * Returns a stream interface for the **file**. | |
| 12 | * | |
| 13 | * Events | |
| 14 | * - **data** {function(item) {}} the data event triggers when a document is ready. | |
| 15 | * - **end** {function() {}} the end event triggers when there is no more documents available. | |
| 16 | * - **close** {function() {}} the close event triggers when the stream is closed. | |
| 17 | * - **error** {function(err) {}} the error event triggers if an error happens. | |
| 18 | * | |
| 19 | * @class Represents a GridFS File Stream. | |
| 20 | * @param {Boolean} autoclose automatically close file when the stream reaches the end. | |
| 21 | * @param {GridStore} cursor a cursor object that the stream wraps. | |
| 22 | * @return {ReadStream} | |
| 23 | */ | |
| 24 | 1 | function ReadStream(autoclose, gstore) { |
| 25 | 0 | if (!(this instanceof ReadStream)) return new ReadStream(autoclose, gstore); |
| 26 | 0 | Stream.call(this); |
| 27 | ||
| 28 | 0 | this.autoclose = !!autoclose; |
| 29 | 0 | this.gstore = gstore; |
| 30 | ||
| 31 | 0 | this.finalLength = gstore.length - gstore.position; |
| 32 | 0 | this.completedLength = 0; |
| 33 | 0 | this.currentChunkNumber = gstore.currentChunk.chunkNumber; |
| 34 | ||
| 35 | 0 | this.paused = false; |
| 36 | 0 | this.readable = true; |
| 37 | 0 | this.pendingChunk = null; |
| 38 | 0 | this.executing = false; |
| 39 | 0 | this.destroyed = false; |
| 40 | ||
| 41 | // Calculate the number of chunks | |
| 42 | 0 | this.numberOfChunks = Math.ceil(gstore.length/gstore.chunkSize); |
| 43 | ||
| 44 | // This seek start position inside the current chunk | |
| 45 | 0 | this.seekStartPosition = gstore.position - (this.currentChunkNumber * gstore.chunkSize); |
| 46 | ||
| 47 | 0 | var self = this; |
| 48 | 0 | processor(function() { |
| 49 | 0 | self._execute(); |
| 50 | }); | |
| 51 | }; | |
| 52 | ||
| 53 | /** | |
| 54 | * Inherit from Stream | |
| 55 | * @ignore | |
| 56 | * @api private | |
| 57 | */ | |
| 58 | 1 | ReadStream.prototype.__proto__ = Stream.prototype; |
| 59 | ||
| 60 | /** | |
| 61 | * Flag stating whether or not this stream is readable. | |
| 62 | */ | |
| 63 | 1 | ReadStream.prototype.readable; |
| 64 | ||
| 65 | /** | |
| 66 | * Flag stating whether or not this stream is paused. | |
| 67 | */ | |
| 68 | 1 | ReadStream.prototype.paused; |
| 69 | ||
| 70 | /** | |
| 71 | * @ignore | |
| 72 | * @api private | |
| 73 | */ | |
| 74 | 1 | ReadStream.prototype._execute = function() { |
| 75 | 0 | if(this.paused === true || this.readable === false) { |
| 76 | 0 | return; |
| 77 | } | |
| 78 | ||
| 79 | 0 | var gstore = this.gstore; |
| 80 | 0 | var self = this; |
| 81 | // Set that we are executing | |
| 82 | 0 | this.executing = true; |
| 83 | ||
| 84 | 0 | var last = false; |
| 85 | 0 | var toRead = 0; |
| 86 | ||
| 87 | 0 | if(gstore.currentChunk.chunkNumber >= (this.numberOfChunks - 1)) { |
| 88 | 0 | self.executing = false; |
| 89 | 0 | last = true; |
| 90 | } | |
| 91 | ||
| 92 | // Data setup | |
| 93 | 0 | var data = null; |
| 94 | ||
| 95 | // Read a slice (with seek set if none) | |
| 96 | 0 | if(this.seekStartPosition > 0 && (gstore.currentChunk.length() - this.seekStartPosition) > 0) { |
| 97 | 0 | data = gstore.currentChunk.readSlice(gstore.currentChunk.length() - this.seekStartPosition); |
| 98 | 0 | this.seekStartPosition = 0; |
| 99 | } else { | |
| 100 | 0 | data = gstore.currentChunk.readSlice(gstore.currentChunk.length()); |
| 101 | } | |
| 102 | ||
| 103 | // Return the data | |
| 104 | 0 | if(data != null && gstore.currentChunk.chunkNumber == self.currentChunkNumber) { |
| 105 | 0 | self.currentChunkNumber = self.currentChunkNumber + 1; |
| 106 | 0 | self.completedLength += data.length; |
| 107 | 0 | self.pendingChunk = null; |
| 108 | 0 | self.emit("data", data); |
| 109 | } | |
| 110 | ||
| 111 | 0 | if(last === true) { |
| 112 | 0 | self.readable = false; |
| 113 | 0 | self.emit("end"); |
| 114 | ||
| 115 | 0 | if(self.autoclose === true) { |
| 116 | 0 | if(gstore.mode[0] == "w") { |
| 117 | 0 | gstore.close(function(err, doc) { |
| 118 | 0 | if (err) { |
| 119 | 0 | console.log("############################## 0") |
| 120 | 0 | self.emit("error", err); |
| 121 | 0 | return; |
| 122 | } | |
| 123 | 0 | self.readable = false; |
| 124 | 0 | self.destroyed = true; |
| 125 | 0 | self.emit("close", doc); |
| 126 | }); | |
| 127 | } else { | |
| 128 | 0 | self.readable = false; |
| 129 | 0 | self.destroyed = true; |
| 130 | 0 | self.emit("close"); |
| 131 | } | |
| 132 | } | |
| 133 | } else { | |
| 134 | 0 | gstore._nthChunk(gstore.currentChunk.chunkNumber + 1, function(err, chunk) { |
| 135 | 0 | if(err) { |
| 136 | 0 | self.readable = false; |
| 137 | 0 | if(self.listeners("error").length > 0) |
| 138 | 0 | self.emit("error", err); |
| 139 | 0 | self.executing = false; |
| 140 | 0 | return; |
| 141 | } | |
| 142 | ||
| 143 | 0 | self.pendingChunk = chunk; |
| 144 | 0 | if(self.paused === true) { |
| 145 | 0 | self.executing = false; |
| 146 | 0 | return; |
| 147 | } | |
| 148 | ||
| 149 | 0 | gstore.currentChunk = self.pendingChunk; |
| 150 | 0 | self._execute(); |
| 151 | }); | |
| 152 | } | |
| 153 | }; | |
| 154 | ||
| 155 | /** | |
| 156 | * Pauses this stream, then no farther events will be fired. | |
| 157 | * | |
| 158 | * @ignore | |
| 159 | * @api public | |
| 160 | */ | |
| 161 | 1 | ReadStream.prototype.pause = function() { |
| 162 | 0 | if(!this.executing) { |
| 163 | 0 | this.paused = true; |
| 164 | } | |
| 165 | }; | |
| 166 | ||
| 167 | /** | |
| 168 | * Destroys the stream, then no farther events will be fired. | |
| 169 | * | |
| 170 | * @ignore | |
| 171 | * @api public | |
| 172 | */ | |
| 173 | 1 | ReadStream.prototype.destroy = function() { |
| 174 | 0 | if(this.destroyed) return; |
| 175 | 0 | this.destroyed = true; |
| 176 | 0 | this.readable = false; |
| 177 | // Emit close event | |
| 178 | 0 | this.emit("close"); |
| 179 | }; | |
| 180 | ||
| 181 | /** | |
| 182 | * Resumes this stream. | |
| 183 | * | |
| 184 | * @ignore | |
| 185 | * @api public | |
| 186 | */ | |
| 187 | 1 | ReadStream.prototype.resume = function() { |
| 188 | 0 | if(this.paused === false || !this.readable) { |
| 189 | 0 | return; |
| 190 | } | |
| 191 | ||
| 192 | 0 | this.paused = false; |
| 193 | 0 | var self = this; |
| 194 | 0 | processor(function() { |
| 195 | 0 | self._execute(); |
| 196 | }); | |
| 197 | }; | |
| 198 | ||
| 199 | 1 | exports.ReadStream = ReadStream; |
| 200 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | try { |
| 2 | 1 | exports.BSONPure = require('bson').BSONPure; |
| 3 | 1 | exports.BSONNative = require('bson').BSONNative; |
| 4 | } catch(err) { | |
| 5 | // do nothing | |
| 6 | } | |
| 7 | ||
| 8 | // export the driver version | |
| 9 | 1 | exports.version = require('../../package').version; |
| 10 | ||
| 11 | 1 | [ 'commands/base_command' |
| 12 | , 'admin' | |
| 13 | , 'collection' | |
| 14 | , 'connection/read_preference' | |
| 15 | , 'connection/connection' | |
| 16 | , 'connection/server' | |
| 17 | , 'connection/mongos' | |
| 18 | , 'connection/repl_set/repl_set' | |
| 19 | , 'mongo_client' | |
| 20 | , 'cursor' | |
| 21 | , 'db' | |
| 22 | , 'mongo_client' | |
| 23 | , 'gridfs/grid' | |
| 24 | , 'gridfs/chunk' | |
| 25 | , 'gridfs/gridstore'].forEach(function (path) { | |
| 26 | 15 | var module = require('./' + path); |
| 27 | 15 | for (var i in module) { |
| 28 | 16 | exports[i] = module[i]; |
| 29 | } | |
| 30 | }); | |
| 31 | ||
| 32 | // backwards compat | |
| 33 | 1 | exports.ReplSetServers = exports.ReplSet; |
| 34 | // Add BSON Classes | |
| 35 | 1 | exports.Binary = require('bson').Binary; |
| 36 | 1 | exports.Code = require('bson').Code; |
| 37 | 1 | exports.DBRef = require('bson').DBRef; |
| 38 | 1 | exports.Double = require('bson').Double; |
| 39 | 1 | exports.Long = require('bson').Long; |
| 40 | 1 | exports.MinKey = require('bson').MinKey; |
| 41 | 1 | exports.MaxKey = require('bson').MaxKey; |
| 42 | 1 | exports.ObjectID = require('bson').ObjectID; |
| 43 | 1 | exports.Symbol = require('bson').Symbol; |
| 44 | 1 | exports.Timestamp = require('bson').Timestamp; |
| 45 | // Add BSON Parser | |
| 46 | 1 | exports.BSON = require('bson').BSONPure.BSON; |
| 47 | ||
| 48 | // Get the Db object | |
| 49 | 1 | var Db = require('./db').Db; |
| 50 | // Set up the connect function | |
| 51 | 1 | var connect = Db.connect; |
| 52 | 1 | var obj = connect; |
| 53 | // Map all values to the exports value | |
| 54 | 1 | for(var name in exports) { |
| 55 | 30 | obj[name] = exports[name]; |
| 56 | } | |
| 57 | ||
| 58 | // Add the pure and native backward compatible functions | |
| 59 | 1 | exports.pure = exports.native = function() { |
| 60 | 0 | return obj; |
| 61 | } | |
| 62 | ||
| 63 | // Map all values to the exports value | |
| 64 | 1 | for(var name in exports) { |
| 65 | 32 | connect[name] = exports[name]; |
| 66 | } | |
| 67 | ||
| 68 | // Set our exports to be the connect function | |
| 69 | 1 | module.exports = connect; |
| 70 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Db = require('./db').Db |
| 2 | , Server = require('./connection/server').Server | |
| 3 | , Mongos = require('./connection/mongos').Mongos | |
| 4 | , ReplSet = require('./connection/repl_set/repl_set').ReplSet | |
| 5 | , ReadPreference = require('./connection/read_preference').ReadPreference | |
| 6 | , inherits = require('util').inherits | |
| 7 | , EventEmitter = require('events').EventEmitter | |
| 8 | , parse = require('./connection/url_parser').parse; | |
| 9 | ||
| 10 | /** | |
| 11 | * Create a new MongoClient instance. | |
| 12 | * | |
| 13 | * Options | |
| 14 | * - **w**, {Number/String, > -1 || 'majority' || tag name} the write concern for the operation where < 1 is no acknowlegement of write and w >= 1, w = 'majority' or tag acknowledges the write | |
| 15 | * - **wtimeout**, {Number, 0} set the timeout for waiting for write concern to finish (combines with w option) | |
| 16 | * - **fsync**, (Boolean, default:false) write waits for fsync before returning | |
| 17 | * - **journal**, (Boolean, default:false) write waits for journal sync before returning | |
| 18 | * - **readPreference** {String}, the prefered read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). | |
| 19 | * - **native_parser** {Boolean, default:false}, use c++ bson parser. | |
| 20 | * - **forceServerObjectId** {Boolean, default:false}, force server to create _id fields instead of client. | |
| 21 | * - **pkFactory** {Object}, object overriding the basic ObjectID primary key generation. | |
| 22 | * - **serializeFunctions** {Boolean, default:false}, serialize functions. | |
| 23 | * - **raw** {Boolean, default:false}, peform operations using raw bson buffers. | |
| 24 | * - **recordQueryStats** {Boolean, default:false}, record query statistics during execution. | |
| 25 | * - **retryMiliSeconds** {Number, default:5000}, number of miliseconds between retries. | |
| 26 | * - **numberOfRetries** {Number, default:5}, number of retries off connection. | |
| 27 | * - **bufferMaxEntries** {Boolean, default: -1}, sets a cap on how many operations the driver will buffer up before giving up on getting a working connection, default is -1 which is unlimited | |
| 28 | * | |
| 29 | * Deprecated Options | |
| 30 | * - **safe** {true | {w:n, wtimeout:n} | {fsync:true}, default:false}, executes with a getLastError command returning the results of the command on MongoDB. | |
| 31 | * | |
| 32 | * @class Represents a MongoClient | |
| 33 | * @param {Object} serverConfig server config object. | |
| 34 | * @param {Object} [options] additional options for the collection. | |
| 35 | */ | |
| 36 | 1 | function MongoClient(serverConfig, options) { |
| 37 | 0 | if(serverConfig != null) { |
| 38 | 0 | options = options == null ? {} : options; |
| 39 | // If no write concern is set set the default to w:1 | |
| 40 | 0 | if(options != null && !options.journal && !options.w && !options.fsync) { |
| 41 | 0 | options.w = 1; |
| 42 | } | |
| 43 | ||
| 44 | // The internal db instance we are wrapping | |
| 45 | 0 | this._db = new Db('test', serverConfig, options); |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * @ignore | |
| 51 | */ | |
| 52 | 1 | inherits(MongoClient, EventEmitter); |
| 53 | ||
| 54 | /** | |
| 55 | * Connect to MongoDB using a url as documented at | |
| 56 | * | |
| 57 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 58 | * | |
| 59 | * Options | |
| 60 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 61 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 62 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 63 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 64 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 65 | * | |
| 66 | * @param {String} url connection url for MongoDB. | |
| 67 | * @param {Object} [options] optional options for insert command | |
| 68 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured. | |
| 69 | * @return {null} | |
| 70 | * @api public | |
| 71 | */ | |
| 72 | 1 | MongoClient.prototype.connect = function(url, options, callback) { |
| 73 | 0 | var self = this; |
| 74 | ||
| 75 | 0 | if(typeof options == 'function') { |
| 76 | 0 | callback = options; |
| 77 | 0 | options = {}; |
| 78 | } | |
| 79 | ||
| 80 | 0 | MongoClient.connect(url, options, function(err, db) { |
| 81 | 0 | if(err) return callback(err, db); |
| 82 | // Store internal db instance reference | |
| 83 | 0 | self._db = db; |
| 84 | // Emit open and perform callback | |
| 85 | 0 | self.emit("open", err, db); |
| 86 | 0 | callback(err, db); |
| 87 | }); | |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Initialize the database connection. | |
| 92 | * | |
| 93 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the connected mongoclient or null if an error occured. | |
| 94 | * @return {null} | |
| 95 | * @api public | |
| 96 | */ | |
| 97 | 1 | MongoClient.prototype.open = function(callback) { |
| 98 | // Self reference | |
| 99 | 0 | var self = this; |
| 100 | // Open the db | |
| 101 | 0 | this._db.open(function(err, db) { |
| 102 | 0 | if(err) return callback(err, null); |
| 103 | // Emit open event | |
| 104 | 0 | self.emit("open", err, db); |
| 105 | // Callback | |
| 106 | 0 | callback(null, self); |
| 107 | }) | |
| 108 | } | |
| 109 | ||
| 110 | /** | |
| 111 | * Close the current db connection, including all the child db instances. Emits close event if no callback is provided. | |
| 112 | * | |
| 113 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the results from the close method or null if an error occured. | |
| 114 | * @return {null} | |
| 115 | * @api public | |
| 116 | */ | |
| 117 | 1 | MongoClient.prototype.close = function(callback) { |
| 118 | 0 | this._db.close(callback); |
| 119 | } | |
| 120 | ||
| 121 | /** | |
| 122 | * Create a new Db instance sharing the current socket connections. | |
| 123 | * | |
| 124 | * @param {String} dbName the name of the database we want to use. | |
| 125 | * @return {Db} a db instance using the new database. | |
| 126 | * @api public | |
| 127 | */ | |
| 128 | 1 | MongoClient.prototype.db = function(dbName) { |
| 129 | 0 | return this._db.db(dbName); |
| 130 | } | |
| 131 | ||
| 132 | /** | |
| 133 | * Connect to MongoDB using a url as documented at | |
| 134 | * | |
| 135 | * docs.mongodb.org/manual/reference/connection-string/ | |
| 136 | * | |
| 137 | * Options | |
| 138 | * - **uri_decode_auth** {Boolean, default:false} uri decode the user name and password for authentication | |
| 139 | * - **db** {Object, default: null} a hash off options to set on the db object, see **Db constructor** | |
| 140 | * - **server** {Object, default: null} a hash off options to set on the server objects, see **Server** constructor** | |
| 141 | * - **replSet** {Object, default: null} a hash off options to set on the replSet object, see **ReplSet** constructor** | |
| 142 | * - **mongos** {Object, default: null} a hash off options to set on the mongos object, see **Mongos** constructor** | |
| 143 | * | |
| 144 | * @param {String} url connection url for MongoDB. | |
| 145 | * @param {Object} [options] optional options for insert command | |
| 146 | * @param {Function} callback this will be called after executing this method. The first parameter will contain the Error object if an error occured, or null otherwise. While the second parameter will contain the initialized db object or null if an error occured. | |
| 147 | * @return {null} | |
| 148 | * @api public | |
| 149 | */ | |
| 150 | 1 | MongoClient.connect = function(url, options, callback) { |
| 151 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 152 | 0 | callback = typeof args[args.length - 1] == 'function' ? args.pop() : null; |
| 153 | 0 | options = args.length ? args.shift() : null; |
| 154 | 0 | options = options || {}; |
| 155 | ||
| 156 | // Set default empty server options | |
| 157 | 0 | var serverOptions = options.server || {}; |
| 158 | 0 | var mongosOptions = options.mongos || {}; |
| 159 | 0 | var replSetServersOptions = options.replSet || options.replSetServers || {}; |
| 160 | 0 | var dbOptions = options.db || {}; |
| 161 | ||
| 162 | // If callback is null throw an exception | |
| 163 | 0 | if(callback == null) |
| 164 | 0 | throw new Error("no callback function provided"); |
| 165 | ||
| 166 | // Parse the string | |
| 167 | 0 | var object = parse(url, options); |
| 168 | // Merge in any options for db in options object | |
| 169 | 0 | if(dbOptions) { |
| 170 | 0 | for(var name in dbOptions) object.db_options[name] = dbOptions[name]; |
| 171 | } | |
| 172 | ||
| 173 | // Added the url to the options | |
| 174 | 0 | object.db_options.url = url; |
| 175 | ||
| 176 | // Merge in any options for server in options object | |
| 177 | 0 | if(serverOptions) { |
| 178 | 0 | for(var name in serverOptions) object.server_options[name] = serverOptions[name]; |
| 179 | } | |
| 180 | ||
| 181 | // Merge in any replicaset server options | |
| 182 | 0 | if(replSetServersOptions) { |
| 183 | 0 | for(var name in replSetServersOptions) object.rs_options[name] = replSetServersOptions[name]; |
| 184 | } | |
| 185 | ||
| 186 | // Merge in any replicaset server options | |
| 187 | 0 | if(mongosOptions) { |
| 188 | 0 | for(var name in mongosOptions) object.mongos_options[name] = mongosOptions[name]; |
| 189 | } | |
| 190 | ||
| 191 | // We need to ensure that the list of servers are only either direct members or mongos | |
| 192 | // they cannot be a mix of monogs and mongod's | |
| 193 | 0 | var totalNumberOfServers = object.servers.length; |
| 194 | 0 | var totalNumberOfMongosServers = 0; |
| 195 | 0 | var totalNumberOfMongodServers = 0; |
| 196 | 0 | var serverConfig = null; |
| 197 | 0 | var errorServers = {}; |
| 198 | ||
| 199 | // Failure modes | |
| 200 | 0 | if(object.servers.length == 0) throw new Error("connection string must contain at least one seed host"); |
| 201 | ||
| 202 | // If we have no db setting for the native parser try to set the c++ one first | |
| 203 | 0 | object.db_options.native_parser = _setNativeParser(object.db_options); |
| 204 | // If no auto_reconnect is set, set it to true as default for single servers | |
| 205 | 0 | if(typeof object.server_options.auto_reconnect != 'boolean') { |
| 206 | 0 | object.server_options.auto_reconnect = true; |
| 207 | } | |
| 208 | ||
| 209 | // If we have more than a server, it could be replicaset or mongos list | |
| 210 | // need to verify that it's one or the other and fail if it's a mix | |
| 211 | // Connect to all servers and run ismaster | |
| 212 | 0 | for(var i = 0; i < object.servers.length; i++) { |
| 213 | // Set up socket options | |
| 214 | 0 | var _server_options = { |
| 215 | poolSize:1 | |
| 216 | , socketOptions: { | |
| 217 | connectTimeoutMS:30000 | |
| 218 | , socketTimeoutMS: 30000 | |
| 219 | } | |
| 220 | , auto_reconnect:false}; | |
| 221 | ||
| 222 | // Ensure we have ssl setup for the servers | |
| 223 | 0 | if(object.rs_options.ssl) { |
| 224 | 0 | _server_options.ssl = object.rs_options.ssl; |
| 225 | 0 | _server_options.sslValidate = object.rs_options.sslValidate; |
| 226 | 0 | _server_options.sslCA = object.rs_options.sslCA; |
| 227 | 0 | _server_options.sslCert = object.rs_options.sslCert; |
| 228 | 0 | _server_options.sslKey = object.rs_options.sslKey; |
| 229 | 0 | _server_options.sslPass = object.rs_options.sslPass; |
| 230 | 0 | } else if(object.server_options.ssl) { |
| 231 | 0 | _server_options.ssl = object.server_options.ssl; |
| 232 | 0 | _server_options.sslValidate = object.server_options.sslValidate; |
| 233 | 0 | _server_options.sslCA = object.server_options.sslCA; |
| 234 | 0 | _server_options.sslCert = object.server_options.sslCert; |
| 235 | 0 | _server_options.sslKey = object.server_options.sslKey; |
| 236 | 0 | _server_options.sslPass = object.server_options.sslPass; |
| 237 | } | |
| 238 | ||
| 239 | // Set up the Server object | |
| 240 | 0 | var _server = object.servers[i].domain_socket |
| 241 | ? new Server(object.servers[i].domain_socket, _server_options) | |
| 242 | : new Server(object.servers[i].host, object.servers[i].port, _server_options); | |
| 243 | ||
| 244 | 0 | var connectFunction = function(__server) { |
| 245 | // Attempt connect | |
| 246 | 0 | new Db(object.dbName, __server, {safe:false, native_parser:false}).open(function(err, db) { |
| 247 | // Update number of servers | |
| 248 | 0 | totalNumberOfServers = totalNumberOfServers - 1; |
| 249 | // If no error do the correct checks | |
| 250 | 0 | if(!err) { |
| 251 | // Close the connection | |
| 252 | 0 | db.close(true); |
| 253 | 0 | var isMasterDoc = db.serverConfig.isMasterDoc; |
| 254 | // Check what type of server we have | |
| 255 | 0 | if(isMasterDoc.setName) totalNumberOfMongodServers++; |
| 256 | 0 | if(isMasterDoc.msg && isMasterDoc.msg == "isdbgrid") totalNumberOfMongosServers++; |
| 257 | } else { | |
| 258 | 0 | errorServers[__server.host + ":" + __server.port] = __server; |
| 259 | } | |
| 260 | ||
| 261 | 0 | if(totalNumberOfServers == 0) { |
| 262 | // If we have a mix of mongod and mongos, throw an error | |
| 263 | 0 | if(totalNumberOfMongosServers > 0 && totalNumberOfMongodServers > 0) { |
| 264 | 0 | return process.nextTick(function() { |
| 265 | 0 | try { |
| 266 | 0 | callback(new Error("cannot combine a list of replicaset seeds and mongos seeds")); |
| 267 | } catch (err) { | |
| 268 | 0 | if(db) db.close(); |
| 269 | 0 | throw err |
| 270 | } | |
| 271 | }) | |
| 272 | } | |
| 273 | ||
| 274 | 0 | if(totalNumberOfMongodServers == 0 && object.servers.length == 1) { |
| 275 | 0 | var obj = object.servers[0]; |
| 276 | 0 | serverConfig = obj.domain_socket ? |
| 277 | new Server(obj.domain_socket, object.server_options) | |
| 278 | : new Server(obj.host, obj.port, object.server_options); | |
| 279 | 0 | } else if(totalNumberOfMongodServers > 0 || totalNumberOfMongosServers > 0) { |
| 280 | 0 | var finalServers = object.servers |
| 281 | .filter(function(serverObj) { | |
| 282 | 0 | return errorServers[serverObj.host + ":" + serverObj.port] == null; |
| 283 | }) | |
| 284 | .map(function(serverObj) { | |
| 285 | 0 | return new Server(serverObj.host, serverObj.port, object.server_options); |
| 286 | }); | |
| 287 | // Clean out any error servers | |
| 288 | 0 | errorServers = {}; |
| 289 | // Set up the final configuration | |
| 290 | 0 | if(totalNumberOfMongodServers > 0) { |
| 291 | 0 | serverConfig = new ReplSet(finalServers, object.rs_options); |
| 292 | } else { | |
| 293 | 0 | serverConfig = new Mongos(finalServers, object.mongos_options); |
| 294 | } | |
| 295 | } | |
| 296 | ||
| 297 | 0 | if(serverConfig == null) { |
| 298 | 0 | return process.nextTick(function() { |
| 299 | 0 | try { |
| 300 | 0 | callback(new Error("Could not locate any valid servers in initial seed list")); |
| 301 | } catch (err) { | |
| 302 | 0 | if(db) db.close(); |
| 303 | 0 | throw err |
| 304 | } | |
| 305 | }); | |
| 306 | } | |
| 307 | // Ensure no firing off open event before we are ready | |
| 308 | 0 | serverConfig.emitOpen = false; |
| 309 | // Set up all options etc and connect to the database | |
| 310 | 0 | _finishConnecting(serverConfig, object, options, callback) |
| 311 | } | |
| 312 | }); | |
| 313 | } | |
| 314 | ||
| 315 | // Wrap the context of the call | |
| 316 | 0 | connectFunction(_server); |
| 317 | } | |
| 318 | } | |
| 319 | ||
| 320 | 1 | var _setNativeParser = function(db_options) { |
| 321 | 0 | if(typeof db_options.native_parser == 'boolean') return db_options.native_parser; |
| 322 | ||
| 323 | 0 | try { |
| 324 | 0 | require('bson').BSONNative.BSON; |
| 325 | 0 | return true; |
| 326 | } catch(err) { | |
| 327 | 0 | return false; |
| 328 | } | |
| 329 | } | |
| 330 | ||
| 331 | 1 | var _finishConnecting = function(serverConfig, object, options, callback) { |
| 332 | // Safe settings | |
| 333 | 0 | var safe = {}; |
| 334 | // Build the safe parameter if needed | |
| 335 | 0 | if(object.db_options.journal) safe.j = object.db_options.journal; |
| 336 | 0 | if(object.db_options.w) safe.w = object.db_options.w; |
| 337 | 0 | if(object.db_options.fsync) safe.fsync = object.db_options.fsync; |
| 338 | 0 | if(object.db_options.wtimeoutMS) safe.wtimeout = object.db_options.wtimeoutMS; |
| 339 | ||
| 340 | // If we have a read Preference set | |
| 341 | 0 | if(object.db_options.read_preference) { |
| 342 | 0 | var readPreference = new ReadPreference(object.db_options.read_preference); |
| 343 | // If we have the tags set up | |
| 344 | 0 | if(object.db_options.read_preference_tags) |
| 345 | 0 | readPreference = new ReadPreference(object.db_options.read_preference, object.db_options.read_preference_tags); |
| 346 | // Add the read preference | |
| 347 | 0 | object.db_options.readPreference = readPreference; |
| 348 | } | |
| 349 | ||
| 350 | // No safe mode if no keys | |
| 351 | 0 | if(Object.keys(safe).length == 0) safe = false; |
| 352 | ||
| 353 | // Add the safe object | |
| 354 | 0 | object.db_options.safe = safe; |
| 355 | ||
| 356 | // Get the socketTimeoutMS | |
| 357 | 0 | var socketTimeoutMS = object.server_options.socketOptions.socketTimeoutMS || 0; |
| 358 | ||
| 359 | // If we have a replset, override with replicaset socket timeout option if available | |
| 360 | 0 | if(serverConfig instanceof ReplSet) { |
| 361 | 0 | socketTimeoutMS = object.rs_options.socketOptions.socketTimeoutMS || socketTimeoutMS; |
| 362 | } | |
| 363 | ||
| 364 | // Set socketTimeout to the same as the connectTimeoutMS or 30 sec | |
| 365 | 0 | serverConfig.connectTimeoutMS = serverConfig.connectTimeoutMS || 30000; |
| 366 | 0 | serverConfig.socketTimeoutMS = serverConfig.connectTimeoutMS; |
| 367 | ||
| 368 | // Set up the db options | |
| 369 | 0 | var db = new Db(object.dbName, serverConfig, object.db_options); |
| 370 | // Open the db | |
| 371 | 0 | db.open(function(err, db){ |
| 372 | 0 | if(err) { |
| 373 | 0 | return process.nextTick(function() { |
| 374 | 0 | try { |
| 375 | 0 | callback(err, null); |
| 376 | } catch (err) { | |
| 377 | 0 | if(db) db.close(); |
| 378 | 0 | throw err |
| 379 | } | |
| 380 | }); | |
| 381 | } | |
| 382 | ||
| 383 | // Reset the socket timeout | |
| 384 | 0 | serverConfig.socketTimeoutMS = socketTimeoutMS || 0; |
| 385 | ||
| 386 | // Set the provided write concern or fall back to w:1 as default | |
| 387 | 0 | if(db.options !== null && !db.options.safe && !db.options.journal |
| 388 | && !db.options.w && !db.options.fsync && typeof db.options.w != 'number' | |
| 389 | && (db.options.safe == false && object.db_options.url.indexOf("safe=") == -1)) { | |
| 390 | 0 | db.options.w = 1; |
| 391 | } | |
| 392 | ||
| 393 | 0 | if(err == null && object.auth){ |
| 394 | // What db to authenticate against | |
| 395 | 0 | var authentication_db = db; |
| 396 | 0 | if(object.db_options && object.db_options.authSource) { |
| 397 | 0 | authentication_db = db.db(object.db_options.authSource); |
| 398 | } | |
| 399 | ||
| 400 | // Build options object | |
| 401 | 0 | var options = {}; |
| 402 | 0 | if(object.db_options.authMechanism) options.authMechanism = object.db_options.authMechanism; |
| 403 | 0 | if(object.db_options.gssapiServiceName) options.gssapiServiceName = object.db_options.gssapiServiceName; |
| 404 | ||
| 405 | // Authenticate | |
| 406 | 0 | authentication_db.authenticate(object.auth.user, object.auth.password, options, function(err, success){ |
| 407 | 0 | if(success){ |
| 408 | 0 | process.nextTick(function() { |
| 409 | 0 | try { |
| 410 | 0 | callback(null, db); |
| 411 | } catch (err) { | |
| 412 | 0 | if(db) db.close(); |
| 413 | 0 | throw err |
| 414 | } | |
| 415 | }); | |
| 416 | } else { | |
| 417 | 0 | if(db) db.close(); |
| 418 | 0 | process.nextTick(function() { |
| 419 | 0 | try { |
| 420 | 0 | callback(err ? err : new Error('Could not authenticate user ' + auth[0]), null); |
| 421 | } catch (err) { | |
| 422 | 0 | if(db) db.close(); |
| 423 | 0 | throw err |
| 424 | } | |
| 425 | }); | |
| 426 | } | |
| 427 | }); | |
| 428 | } else { | |
| 429 | 0 | process.nextTick(function() { |
| 430 | 0 | try { |
| 431 | 0 | callback(err, db); |
| 432 | } catch (err) { | |
| 433 | 0 | if(db) db.close(); |
| 434 | 0 | throw err |
| 435 | } | |
| 436 | }) | |
| 437 | } | |
| 438 | }); | |
| 439 | } | |
| 440 | ||
| 441 | 1 | exports.MongoClient = MongoClient; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('bson').Long |
| 2 | , timers = require('timers'); | |
| 3 | ||
| 4 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 5 | 1 | var processor = require('../utils').processor(); |
| 6 | ||
| 7 | /** | |
| 8 | Reply message from mongo db | |
| 9 | **/ | |
| 10 | 1 | var MongoReply = exports.MongoReply = function() { |
| 11 | 0 | this.documents = []; |
| 12 | 0 | this.index = 0; |
| 13 | }; | |
| 14 | ||
| 15 | 1 | MongoReply.prototype.parseHeader = function(binary_reply, bson) { |
| 16 | // Unpack the standard header first | |
| 17 | 0 | this.messageLength = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 18 | 0 | this.index = this.index + 4; |
| 19 | // Fetch the request id for this reply | |
| 20 | 0 | this.requestId = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 21 | 0 | this.index = this.index + 4; |
| 22 | // Fetch the id of the request that triggered the response | |
| 23 | 0 | this.responseTo = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 24 | // Skip op-code field | |
| 25 | 0 | this.index = this.index + 4 + 4; |
| 26 | // Unpack the reply message | |
| 27 | 0 | this.responseFlag = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 28 | 0 | this.index = this.index + 4; |
| 29 | // Unpack the cursor id (a 64 bit long integer) | |
| 30 | 0 | var low_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 31 | 0 | this.index = this.index + 4; |
| 32 | 0 | var high_bits = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 33 | 0 | this.index = this.index + 4; |
| 34 | 0 | this.cursorId = new Long(low_bits, high_bits); |
| 35 | // Unpack the starting from | |
| 36 | 0 | this.startingFrom = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 37 | 0 | this.index = this.index + 4; |
| 38 | // Unpack the number of objects returned | |
| 39 | 0 | this.numberReturned = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 40 | 0 | this.index = this.index + 4; |
| 41 | } | |
| 42 | ||
| 43 | 1 | MongoReply.prototype.parseBody = function(binary_reply, bson, raw, callback) { |
| 44 | 0 | raw = raw == null ? false : raw; |
| 45 | ||
| 46 | 0 | try { |
| 47 | // Let's unpack all the bson documents, deserialize them and store them | |
| 48 | 0 | for(var object_index = 0; object_index < this.numberReturned; object_index++) { |
| 49 | 0 | var _options = {promoteLongs: bson.promoteLongs}; |
| 50 | ||
| 51 | // Read the size of the bson object | |
| 52 | 0 | var bsonObjectSize = binary_reply[this.index] | binary_reply[this.index + 1] << 8 | binary_reply[this.index + 2] << 16 | binary_reply[this.index + 3] << 24; |
| 53 | ||
| 54 | // If we are storing the raw responses to pipe straight through | |
| 55 | 0 | if(raw) { |
| 56 | // Deserialize the object and add to the documents array | |
| 57 | 0 | this.documents.push(binary_reply.slice(this.index, this.index + bsonObjectSize)); |
| 58 | } else { | |
| 59 | // Deserialize the object and add to the documents array | |
| 60 | 0 | this.documents.push(bson.deserialize(binary_reply.slice(this.index, this.index + bsonObjectSize), _options)); |
| 61 | } | |
| 62 | ||
| 63 | // Adjust binary index to point to next block of binary bson data | |
| 64 | 0 | this.index = this.index + bsonObjectSize; |
| 65 | } | |
| 66 | ||
| 67 | // No error return | |
| 68 | 0 | callback(null); |
| 69 | } catch(err) { | |
| 70 | 0 | return callback(err); |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | 1 | MongoReply.prototype.is_error = function(){ |
| 75 | 0 | if(this.documents.length == 1) { |
| 76 | 0 | return this.documents[0].ok == 1 ? false : true; |
| 77 | } | |
| 78 | 0 | return false; |
| 79 | }; | |
| 80 | ||
| 81 | 1 | MongoReply.prototype.error_message = function() { |
| 82 | 0 | return this.documents.length == 1 && this.documents[0].ok == 1 ? '' : this.documents[0].errmsg; |
| 83 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Cursor2 = require('./cursor').Cursor |
| 2 | , Readable = require('stream').Readable | |
| 3 | , utils = require('./utils') | |
| 4 | , inherits = require('util').inherits; | |
| 5 | ||
| 6 | 1 | var Cursor = function Cursor(_scope_options, _cursor) { |
| 7 | // | |
| 8 | // Backward compatible methods | |
| 9 | 0 | this.toArray = function(callback) { |
| 10 | 0 | return _cursor.toArray(callback); |
| 11 | } | |
| 12 | ||
| 13 | 0 | this.each = function(callback) { |
| 14 | 0 | return _cursor.each(callback); |
| 15 | } | |
| 16 | ||
| 17 | 0 | this.next = function(callback) { |
| 18 | 0 | this.nextObject(callback); |
| 19 | } | |
| 20 | ||
| 21 | 0 | this.nextObject = function(callback) { |
| 22 | 0 | return _cursor.nextObject(callback); |
| 23 | } | |
| 24 | ||
| 25 | 0 | this.setReadPreference = function(readPreference, callback) { |
| 26 | 0 | _scope_options.readPreference = {readPreference: readPreference}; |
| 27 | 0 | _cursor.setReadPreference(readPreference, callback); |
| 28 | 0 | return this; |
| 29 | } | |
| 30 | ||
| 31 | 0 | this.batchSize = function(batchSize, callback) { |
| 32 | 0 | _scope_options.batchSize = batchSize; |
| 33 | 0 | _cursor.batchSize(_scope_options.batchSize, callback); |
| 34 | 0 | return this; |
| 35 | } | |
| 36 | ||
| 37 | 0 | this.count = function(applySkipLimit, callback) { |
| 38 | 0 | return _cursor.count(applySkipLimit, callback); |
| 39 | } | |
| 40 | ||
| 41 | 0 | this.stream = function(options) { |
| 42 | 0 | return _cursor.stream(options); |
| 43 | } | |
| 44 | ||
| 45 | 0 | this.close = function(callback) { |
| 46 | 0 | return _cursor.close(callback); |
| 47 | } | |
| 48 | ||
| 49 | 0 | this.explain = function(callback) { |
| 50 | 0 | return _cursor.explain(callback); |
| 51 | } | |
| 52 | ||
| 53 | 0 | this.isClosed = function(callback) { |
| 54 | 0 | return _cursor.isClosed(); |
| 55 | } | |
| 56 | ||
| 57 | 0 | this.rewind = function() { |
| 58 | 0 | return _cursor.rewind(); |
| 59 | } | |
| 60 | ||
| 61 | // Internal methods | |
| 62 | 0 | this.limit = function(limit, callback) { |
| 63 | 0 | _cursor.limit(limit, callback); |
| 64 | 0 | _scope_options.limit = limit; |
| 65 | 0 | return this; |
| 66 | } | |
| 67 | ||
| 68 | 0 | this.skip = function(skip, callback) { |
| 69 | 0 | _cursor.skip(skip, callback); |
| 70 | 0 | _scope_options.skip = skip; |
| 71 | 0 | return this; |
| 72 | } | |
| 73 | ||
| 74 | 0 | this.hint = function(hint) { |
| 75 | 0 | _scope_options.hint = hint; |
| 76 | 0 | _cursor.hint = _scope_options.hint; |
| 77 | 0 | return this; |
| 78 | } | |
| 79 | ||
| 80 | 0 | this.maxTimeMS = function(maxTimeMS) { |
| 81 | 0 | _cursor.maxTimeMS(maxTimeMS) |
| 82 | 0 | _scope_options.maxTimeMS = maxTimeMS; |
| 83 | 0 | return this; |
| 84 | }, | |
| 85 | ||
| 86 | this.sort = function(keyOrList, direction, callback) { | |
| 87 | 0 | _cursor.sort(keyOrList, direction, callback); |
| 88 | 0 | _scope_options.sort = keyOrList; |
| 89 | 0 | return this; |
| 90 | }, | |
| 91 | ||
| 92 | this.fields = function(fields) { | |
| 93 | 0 | _fields = fields; |
| 94 | 0 | _cursor.fields = _fields; |
| 95 | 0 | return this; |
| 96 | } | |
| 97 | ||
| 98 | // | |
| 99 | // Backward compatible settings | |
| 100 | 0 | Object.defineProperty(this, "timeout", { |
| 101 | get: function() { | |
| 102 | 0 | return _cursor.timeout; |
| 103 | } | |
| 104 | }); | |
| 105 | ||
| 106 | 0 | Object.defineProperty(this, "read", { |
| 107 | get: function() { | |
| 108 | 0 | return _cursor.read; |
| 109 | } | |
| 110 | }); | |
| 111 | ||
| 112 | 0 | Object.defineProperty(this, "items", { |
| 113 | get: function() { | |
| 114 | 0 | return _cursor.items; |
| 115 | } | |
| 116 | }); | |
| 117 | } | |
| 118 | ||
| 119 | 1 | var Scope = function(collection, _selector, _fields, _scope_options) { |
| 120 | 0 | var self = this; |
| 121 | ||
| 122 | // Ensure we have at least an empty cursor options object | |
| 123 | 0 | _scope_options = _scope_options || {}; |
| 124 | 0 | var _write_concern = _scope_options.write_concern || null; |
| 125 | ||
| 126 | // Ensure default read preference | |
| 127 | 0 | if(!_scope_options.readPreference) _scope_options.readPreference = {readPreference: 'primary'}; |
| 128 | ||
| 129 | // Set up the cursor | |
| 130 | 0 | var _cursor = new Cursor2( |
| 131 | collection.db, collection, _selector | |
| 132 | , _fields, _scope_options | |
| 133 | ); | |
| 134 | ||
| 135 | // Write branch options | |
| 136 | 0 | var writeOptions = { |
| 137 | insert: function(documents, callback) { | |
| 138 | // Merge together options | |
| 139 | 0 | var options = _write_concern || {}; |
| 140 | // Execute insert | |
| 141 | 0 | collection.insert(documents, options, callback); |
| 142 | }, | |
| 143 | ||
| 144 | save: function(document, callback) { | |
| 145 | // Merge together options | |
| 146 | 0 | var save_options = _write_concern || {}; |
| 147 | // Execute save | |
| 148 | 0 | collection.save(document, save_options, function(err, result) { |
| 149 | 0 | if(typeof result == 'number' && result == 1) { |
| 150 | 0 | return callback(null, document); |
| 151 | } | |
| 152 | ||
| 153 | 0 | return callback(null, document); |
| 154 | }); | |
| 155 | }, | |
| 156 | ||
| 157 | find: function(selector) { | |
| 158 | 0 | _selector = selector; |
| 159 | 0 | return writeOptions; |
| 160 | }, | |
| 161 | ||
| 162 | // | |
| 163 | // Update is implicit multiple document update | |
| 164 | update: function(operations, callback) { | |
| 165 | // Merge together options | |
| 166 | 0 | var update_options = _write_concern || {}; |
| 167 | ||
| 168 | // Set up options, multi is default operation | |
| 169 | 0 | update_options.multi = _scope_options.multi ? _scope_options.multi : true; |
| 170 | 0 | if(_scope_options.upsert) update_options.upsert = _scope_options.upsert; |
| 171 | ||
| 172 | // Execute options | |
| 173 | 0 | collection.update(_selector, operations, update_options, function(err, result, obj) { |
| 174 | 0 | callback(err, obj); |
| 175 | }); | |
| 176 | }, | |
| 177 | } | |
| 178 | ||
| 179 | // Set write concern | |
| 180 | 0 | this.withWriteConcern = function(write_concern) { |
| 181 | // Save the current write concern to the Scope | |
| 182 | 0 | _scope_options.write_concern = write_concern; |
| 183 | 0 | _write_concern = write_concern; |
| 184 | // Only allow legal options | |
| 185 | 0 | return writeOptions; |
| 186 | } | |
| 187 | ||
| 188 | // Start find | |
| 189 | 0 | this.find = function(selector, options) { |
| 190 | // Save the current selector | |
| 191 | 0 | _selector = selector; |
| 192 | // Set the cursor | |
| 193 | 0 | _cursor.selector = selector; |
| 194 | // Return only legal read options | |
| 195 | 0 | return new Cursor(_scope_options, _cursor); |
| 196 | } | |
| 197 | } | |
| 198 | ||
| 199 | 1 | exports.Scope = Scope; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var timers = require('timers'); |
| 2 | ||
| 3 | /** | |
| 4 | * Sort functions, Normalize and prepare sort parameters | |
| 5 | */ | |
| 6 | 1 | var formatSortValue = exports.formatSortValue = function(sortDirection) { |
| 7 | 0 | var value = ("" + sortDirection).toLowerCase(); |
| 8 | ||
| 9 | 0 | switch (value) { |
| 10 | case 'ascending': | |
| 11 | case 'asc': | |
| 12 | case '1': | |
| 13 | 0 | return 1; |
| 14 | case 'descending': | |
| 15 | case 'desc': | |
| 16 | case '-1': | |
| 17 | 0 | return -1; |
| 18 | default: | |
| 19 | 0 | throw new Error("Illegal sort clause, must be of the form " |
| 20 | + "[['field1', '(ascending|descending)'], " | |
| 21 | + "['field2', '(ascending|descending)']]"); | |
| 22 | } | |
| 23 | }; | |
| 24 | ||
| 25 | 1 | var formattedOrderClause = exports.formattedOrderClause = function(sortValue) { |
| 26 | 0 | var orderBy = {}; |
| 27 | ||
| 28 | 0 | if (Array.isArray(sortValue)) { |
| 29 | 0 | for(var i = 0; i < sortValue.length; i++) { |
| 30 | 0 | if(sortValue[i].constructor == String) { |
| 31 | 0 | orderBy[sortValue[i]] = 1; |
| 32 | } else { | |
| 33 | 0 | orderBy[sortValue[i][0]] = formatSortValue(sortValue[i][1]); |
| 34 | } | |
| 35 | } | |
| 36 | 0 | } else if(Object.prototype.toString.call(sortValue) === '[object Object]') { |
| 37 | 0 | orderBy = sortValue; |
| 38 | 0 | } else if (sortValue.constructor == String) { |
| 39 | 0 | orderBy[sortValue] = 1; |
| 40 | } else { | |
| 41 | 0 | throw new Error("Illegal sort clause, must be of the form " + |
| 42 | "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"); | |
| 43 | } | |
| 44 | ||
| 45 | 0 | return orderBy; |
| 46 | }; | |
| 47 | ||
| 48 | 1 | exports.encodeInt = function(value) { |
| 49 | 0 | var buffer = new Buffer(4); |
| 50 | 0 | buffer[3] = (value >> 24) & 0xff; |
| 51 | 0 | buffer[2] = (value >> 16) & 0xff; |
| 52 | 0 | buffer[1] = (value >> 8) & 0xff; |
| 53 | 0 | buffer[0] = value & 0xff; |
| 54 | 0 | return buffer; |
| 55 | } | |
| 56 | ||
| 57 | 1 | exports.encodeIntInPlace = function(value, buffer, index) { |
| 58 | 0 | buffer[index + 3] = (value >> 24) & 0xff; |
| 59 | 0 | buffer[index + 2] = (value >> 16) & 0xff; |
| 60 | 0 | buffer[index + 1] = (value >> 8) & 0xff; |
| 61 | 0 | buffer[index] = value & 0xff; |
| 62 | } | |
| 63 | ||
| 64 | 1 | exports.encodeCString = function(string) { |
| 65 | 0 | var buf = new Buffer(string, 'utf8'); |
| 66 | 0 | return [buf, new Buffer([0])]; |
| 67 | } | |
| 68 | ||
| 69 | 1 | exports.decodeUInt32 = function(array, index) { |
| 70 | 0 | return array[index] | array[index + 1] << 8 | array[index + 2] << 16 | array[index + 3] << 24; |
| 71 | } | |
| 72 | ||
| 73 | // Decode the int | |
| 74 | 1 | exports.decodeUInt8 = function(array, index) { |
| 75 | 0 | return array[index]; |
| 76 | } | |
| 77 | ||
| 78 | /** | |
| 79 | * Context insensitive type checks | |
| 80 | */ | |
| 81 | ||
| 82 | 1 | var toString = Object.prototype.toString; |
| 83 | ||
| 84 | 1 | exports.isObject = function (arg) { |
| 85 | 0 | return '[object Object]' == toString.call(arg) |
| 86 | } | |
| 87 | ||
| 88 | 1 | exports.isArray = function (arg) { |
| 89 | 0 | return Array.isArray(arg) || |
| 90 | 'object' == typeof arg && '[object Array]' == toString.call(arg) | |
| 91 | } | |
| 92 | ||
| 93 | 1 | exports.isDate = function (arg) { |
| 94 | 0 | return 'object' == typeof arg && '[object Date]' == toString.call(arg) |
| 95 | } | |
| 96 | ||
| 97 | 1 | exports.isRegExp = function (arg) { |
| 98 | 0 | return 'object' == typeof arg && '[object RegExp]' == toString.call(arg) |
| 99 | } | |
| 100 | ||
| 101 | /** | |
| 102 | * Wrap a Mongo error document in an Error instance | |
| 103 | * @ignore | |
| 104 | * @api private | |
| 105 | */ | |
| 106 | 1 | var toError = function(error) { |
| 107 | 0 | if (error instanceof Error) return error; |
| 108 | ||
| 109 | 0 | var msg = error.err || error.errmsg || error.errMessage || error; |
| 110 | 0 | var e = new Error(msg); |
| 111 | 0 | e.name = 'MongoError'; |
| 112 | ||
| 113 | // Get all object keys | |
| 114 | 0 | var keys = typeof error == 'object' |
| 115 | ? Object.keys(error) | |
| 116 | : []; | |
| 117 | ||
| 118 | 0 | for(var i = 0; i < keys.length; i++) { |
| 119 | 0 | e[keys[i]] = error[keys[i]]; |
| 120 | } | |
| 121 | ||
| 122 | 0 | return e; |
| 123 | } | |
| 124 | 1 | exports.toError = toError; |
| 125 | ||
| 126 | /** | |
| 127 | * Convert a single level object to an array | |
| 128 | * @ignore | |
| 129 | * @api private | |
| 130 | */ | |
| 131 | 1 | exports.objectToArray = function(object) { |
| 132 | 0 | var list = []; |
| 133 | ||
| 134 | 0 | for(var name in object) { |
| 135 | 0 | list.push(object[name]) |
| 136 | } | |
| 137 | ||
| 138 | 0 | return list; |
| 139 | } | |
| 140 | ||
| 141 | /** | |
| 142 | * Handle single command document return | |
| 143 | * @ignore | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | 1 | exports.handleSingleCommandResultReturn = function(override_value_true, override_value_false, callback) { |
| 147 | 0 | return function(err, result, connection) { |
| 148 | 0 | if(err && typeof callback == 'function') return callback(err, null); |
| 149 | 0 | if(!result || !result.documents || result.documents.length == 0) |
| 150 | 0 | if(typeof callback == 'function') return callback(toError("command failed to return results"), null) |
| 151 | 0 | if(result && result.documents[0].ok == 1) { |
| 152 | 0 | if(override_value_true) return callback(null, override_value_true) |
| 153 | 0 | if(typeof callback == 'function') return callback(null, result.documents[0]); |
| 154 | } | |
| 155 | ||
| 156 | // Return the error from the document | |
| 157 | 0 | if(typeof callback == 'function') return callback(toError(result.documents[0]), override_value_false); |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | /** | |
| 162 | * Return correct processor | |
| 163 | * @ignore | |
| 164 | * @api private | |
| 165 | */ | |
| 166 | 1 | exports.processor = function() { |
| 167 | // Set processor, setImmediate if 0.10 otherwise nextTick | |
| 168 | 10 | process.maxTickDepth = Infinity; |
| 169 | // Only use nextTick | |
| 170 | 10 | return process.nextTick; |
| 171 | } | |
| 172 | ||
| 173 | /** | |
| 174 | * Allow setting the socketTimeoutMS on all connections | |
| 175 | * to work around issues such as secondaries blocking due to compaction | |
| 176 | * | |
| 177 | * @ignore | |
| 178 | * @api private | |
| 179 | */ | |
| 180 | 1 | exports.setSocketTimeoutProperty = function(self, options) { |
| 181 | 1 | Object.defineProperty(self, "socketTimeoutMS", { |
| 182 | enumerable: true | |
| 183 | 0 | , get: function () { return options.socketTimeoutMS; } |
| 184 | , set: function (value) { | |
| 185 | // Set the socket timeoutMS value | |
| 186 | 0 | options.socketTimeoutMS = value; |
| 187 | ||
| 188 | // Get all the connections | |
| 189 | 0 | var connections = self.allRawConnections(); |
| 190 | 0 | for(var i = 0; i < connections.length; i++) { |
| 191 | 0 | connections[i].socketTimeoutMS = value; |
| 192 | } | |
| 193 | } | |
| 194 | }); | |
| 195 | } | |
| 196 | ||
| 197 | 1 | exports.hasWriteCommands = function(connection) { |
| 198 | 0 | return connection != null && connection.serverCapabilities != null && connection.serverCapabilities.hasWriteCommands; |
| 199 | } | |
| 200 | ||
| 201 | ||
| 202 | ||
| 203 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | if(typeof window === 'undefined') { |
| 5 | 1 | var Buffer = require('buffer').Buffer; // TODO just use global Buffer |
| 6 | } | |
| 7 | ||
| 8 | // Binary default subtype | |
| 9 | 1 | var BSON_BINARY_SUBTYPE_DEFAULT = 0; |
| 10 | ||
| 11 | /** | |
| 12 | * @ignore | |
| 13 | * @api private | |
| 14 | */ | |
| 15 | 1 | var writeStringToArray = function(data) { |
| 16 | // Create a buffer | |
| 17 | 0 | var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(data.length)) : new Array(data.length); |
| 18 | // Write the content to the buffer | |
| 19 | 0 | for(var i = 0; i < data.length; i++) { |
| 20 | 0 | buffer[i] = data.charCodeAt(i); |
| 21 | } | |
| 22 | // Write the string to the buffer | |
| 23 | 0 | return buffer; |
| 24 | } | |
| 25 | ||
| 26 | /** | |
| 27 | * Convert Array ot Uint8Array to Binary String | |
| 28 | * | |
| 29 | * @ignore | |
| 30 | * @api private | |
| 31 | */ | |
| 32 | 1 | var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { |
| 33 | 0 | var result = ""; |
| 34 | 0 | for(var i = startIndex; i < endIndex; i++) { |
| 35 | 0 | result = result + String.fromCharCode(byteArray[i]); |
| 36 | } | |
| 37 | 0 | return result; |
| 38 | }; | |
| 39 | ||
| 40 | /** | |
| 41 | * A class representation of the BSON Binary type. | |
| 42 | * | |
| 43 | * Sub types | |
| 44 | * - **BSON.BSON_BINARY_SUBTYPE_DEFAULT**, default BSON type. | |
| 45 | * - **BSON.BSON_BINARY_SUBTYPE_FUNCTION**, BSON function type. | |
| 46 | * - **BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY**, BSON byte array type. | |
| 47 | * - **BSON.BSON_BINARY_SUBTYPE_UUID**, BSON uuid type. | |
| 48 | * - **BSON.BSON_BINARY_SUBTYPE_MD5**, BSON md5 type. | |
| 49 | * - **BSON.BSON_BINARY_SUBTYPE_USER_DEFINED**, BSON user defined type. | |
| 50 | * | |
| 51 | * @class Represents the Binary BSON type. | |
| 52 | * @param {Buffer} buffer a buffer object containing the binary data. | |
| 53 | * @param {Number} [subType] the option binary type. | |
| 54 | * @return {Grid} | |
| 55 | */ | |
| 56 | 1 | function Binary(buffer, subType) { |
| 57 | 0 | if(!(this instanceof Binary)) return new Binary(buffer, subType); |
| 58 | ||
| 59 | 0 | this._bsontype = 'Binary'; |
| 60 | ||
| 61 | 0 | if(buffer instanceof Number) { |
| 62 | 0 | this.sub_type = buffer; |
| 63 | 0 | this.position = 0; |
| 64 | } else { | |
| 65 | 0 | this.sub_type = subType == null ? BSON_BINARY_SUBTYPE_DEFAULT : subType; |
| 66 | 0 | this.position = 0; |
| 67 | } | |
| 68 | ||
| 69 | 0 | if(buffer != null && !(buffer instanceof Number)) { |
| 70 | // Only accept Buffer, Uint8Array or Arrays | |
| 71 | 0 | if(typeof buffer == 'string') { |
| 72 | // Different ways of writing the length of the string for the different types | |
| 73 | 0 | if(typeof Buffer != 'undefined') { |
| 74 | 0 | this.buffer = new Buffer(buffer); |
| 75 | 0 | } else if(typeof Uint8Array != 'undefined' || (Object.prototype.toString.call(buffer) == '[object Array]')) { |
| 76 | 0 | this.buffer = writeStringToArray(buffer); |
| 77 | } else { | |
| 78 | 0 | throw new Error("only String, Buffer, Uint8Array or Array accepted"); |
| 79 | } | |
| 80 | } else { | |
| 81 | 0 | this.buffer = buffer; |
| 82 | } | |
| 83 | 0 | this.position = buffer.length; |
| 84 | } else { | |
| 85 | 0 | if(typeof Buffer != 'undefined') { |
| 86 | 0 | this.buffer = new Buffer(Binary.BUFFER_SIZE); |
| 87 | 0 | } else if(typeof Uint8Array != 'undefined'){ |
| 88 | 0 | this.buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE)); |
| 89 | } else { | |
| 90 | 0 | this.buffer = new Array(Binary.BUFFER_SIZE); |
| 91 | } | |
| 92 | // Set position to start of buffer | |
| 93 | 0 | this.position = 0; |
| 94 | } | |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Updates this binary with byte_value. | |
| 99 | * | |
| 100 | * @param {Character} byte_value a single byte we wish to write. | |
| 101 | * @api public | |
| 102 | */ | |
| 103 | 1 | Binary.prototype.put = function put(byte_value) { |
| 104 | // If it's a string and a has more than one character throw an error | |
| 105 | 0 | if(byte_value['length'] != null && typeof byte_value != 'number' && byte_value.length != 1) throw new Error("only accepts single character String, Uint8Array or Array"); |
| 106 | 0 | if(typeof byte_value != 'number' && byte_value < 0 || byte_value > 255) throw new Error("only accepts number in a valid unsigned byte range 0-255"); |
| 107 | ||
| 108 | // Decode the byte value once | |
| 109 | 0 | var decoded_byte = null; |
| 110 | 0 | if(typeof byte_value == 'string') { |
| 111 | 0 | decoded_byte = byte_value.charCodeAt(0); |
| 112 | 0 | } else if(byte_value['length'] != null) { |
| 113 | 0 | decoded_byte = byte_value[0]; |
| 114 | } else { | |
| 115 | 0 | decoded_byte = byte_value; |
| 116 | } | |
| 117 | ||
| 118 | 0 | if(this.buffer.length > this.position) { |
| 119 | 0 | this.buffer[this.position++] = decoded_byte; |
| 120 | } else { | |
| 121 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 122 | // Create additional overflow buffer | |
| 123 | 0 | var buffer = new Buffer(Binary.BUFFER_SIZE + this.buffer.length); |
| 124 | // Combine the two buffers together | |
| 125 | 0 | this.buffer.copy(buffer, 0, 0, this.buffer.length); |
| 126 | 0 | this.buffer = buffer; |
| 127 | 0 | this.buffer[this.position++] = decoded_byte; |
| 128 | } else { | |
| 129 | 0 | var buffer = null; |
| 130 | // Create a new buffer (typed or normal array) | |
| 131 | 0 | if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { |
| 132 | 0 | buffer = new Uint8Array(new ArrayBuffer(Binary.BUFFER_SIZE + this.buffer.length)); |
| 133 | } else { | |
| 134 | 0 | buffer = new Array(Binary.BUFFER_SIZE + this.buffer.length); |
| 135 | } | |
| 136 | ||
| 137 | // We need to copy all the content to the new array | |
| 138 | 0 | for(var i = 0; i < this.buffer.length; i++) { |
| 139 | 0 | buffer[i] = this.buffer[i]; |
| 140 | } | |
| 141 | ||
| 142 | // Reassign the buffer | |
| 143 | 0 | this.buffer = buffer; |
| 144 | // Write the byte | |
| 145 | 0 | this.buffer[this.position++] = decoded_byte; |
| 146 | } | |
| 147 | } | |
| 148 | }; | |
| 149 | ||
| 150 | /** | |
| 151 | * Writes a buffer or string to the binary. | |
| 152 | * | |
| 153 | * @param {Buffer|String} string a string or buffer to be written to the Binary BSON object. | |
| 154 | * @param {Number} offset specify the binary of where to write the content. | |
| 155 | * @api public | |
| 156 | */ | |
| 157 | 1 | Binary.prototype.write = function write(string, offset) { |
| 158 | 0 | offset = typeof offset == 'number' ? offset : this.position; |
| 159 | ||
| 160 | // If the buffer is to small let's extend the buffer | |
| 161 | 0 | if(this.buffer.length < offset + string.length) { |
| 162 | 0 | var buffer = null; |
| 163 | // If we are in node.js | |
| 164 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 165 | 0 | buffer = new Buffer(this.buffer.length + string.length); |
| 166 | 0 | this.buffer.copy(buffer, 0, 0, this.buffer.length); |
| 167 | 0 | } else if(Object.prototype.toString.call(this.buffer) == '[object Uint8Array]') { |
| 168 | // Create a new buffer | |
| 169 | 0 | buffer = new Uint8Array(new ArrayBuffer(this.buffer.length + string.length)) |
| 170 | // Copy the content | |
| 171 | 0 | for(var i = 0; i < this.position; i++) { |
| 172 | 0 | buffer[i] = this.buffer[i]; |
| 173 | } | |
| 174 | } | |
| 175 | ||
| 176 | // Assign the new buffer | |
| 177 | 0 | this.buffer = buffer; |
| 178 | } | |
| 179 | ||
| 180 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(string) && Buffer.isBuffer(this.buffer)) { |
| 181 | 0 | string.copy(this.buffer, offset, 0, string.length); |
| 182 | 0 | this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; |
| 183 | // offset = string.length | |
| 184 | 0 | } else if(typeof Buffer != 'undefined' && typeof string == 'string' && Buffer.isBuffer(this.buffer)) { |
| 185 | 0 | this.buffer.write(string, 'binary', offset); |
| 186 | 0 | this.position = (offset + string.length) > this.position ? (offset + string.length) : this.position; |
| 187 | // offset = string.length; | |
| 188 | 0 | } else if(Object.prototype.toString.call(string) == '[object Uint8Array]' |
| 189 | || Object.prototype.toString.call(string) == '[object Array]' && typeof string != 'string') { | |
| 190 | 0 | for(var i = 0; i < string.length; i++) { |
| 191 | 0 | this.buffer[offset++] = string[i]; |
| 192 | } | |
| 193 | ||
| 194 | 0 | this.position = offset > this.position ? offset : this.position; |
| 195 | 0 | } else if(typeof string == 'string') { |
| 196 | 0 | for(var i = 0; i < string.length; i++) { |
| 197 | 0 | this.buffer[offset++] = string.charCodeAt(i); |
| 198 | } | |
| 199 | ||
| 200 | 0 | this.position = offset > this.position ? offset : this.position; |
| 201 | } | |
| 202 | }; | |
| 203 | ||
| 204 | /** | |
| 205 | * Reads **length** bytes starting at **position**. | |
| 206 | * | |
| 207 | * @param {Number} position read from the given position in the Binary. | |
| 208 | * @param {Number} length the number of bytes to read. | |
| 209 | * @return {Buffer} | |
| 210 | * @api public | |
| 211 | */ | |
| 212 | 1 | Binary.prototype.read = function read(position, length) { |
| 213 | 0 | length = length && length > 0 |
| 214 | ? length | |
| 215 | : this.position; | |
| 216 | ||
| 217 | // Let's return the data based on the type we have | |
| 218 | 0 | if(this.buffer['slice']) { |
| 219 | 0 | return this.buffer.slice(position, position + length); |
| 220 | } else { | |
| 221 | // Create a buffer to keep the result | |
| 222 | 0 | var buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(length)) : new Array(length); |
| 223 | 0 | for(var i = 0; i < length; i++) { |
| 224 | 0 | buffer[i] = this.buffer[position++]; |
| 225 | } | |
| 226 | } | |
| 227 | // Return the buffer | |
| 228 | 0 | return buffer; |
| 229 | }; | |
| 230 | ||
| 231 | /** | |
| 232 | * Returns the value of this binary as a string. | |
| 233 | * | |
| 234 | * @return {String} | |
| 235 | * @api public | |
| 236 | */ | |
| 237 | 1 | Binary.prototype.value = function value(asRaw) { |
| 238 | 0 | asRaw = asRaw == null ? false : asRaw; |
| 239 | ||
| 240 | // If it's a node.js buffer object | |
| 241 | 0 | if(typeof Buffer != 'undefined' && Buffer.isBuffer(this.buffer)) { |
| 242 | 0 | return asRaw ? this.buffer.slice(0, this.position) : this.buffer.toString('binary', 0, this.position); |
| 243 | } else { | |
| 244 | 0 | if(asRaw) { |
| 245 | // we support the slice command use it | |
| 246 | 0 | if(this.buffer['slice'] != null) { |
| 247 | 0 | return this.buffer.slice(0, this.position); |
| 248 | } else { | |
| 249 | // Create a new buffer to copy content to | |
| 250 | 0 | var newBuffer = Object.prototype.toString.call(this.buffer) == '[object Uint8Array]' ? new Uint8Array(new ArrayBuffer(this.position)) : new Array(this.position); |
| 251 | // Copy content | |
| 252 | 0 | for(var i = 0; i < this.position; i++) { |
| 253 | 0 | newBuffer[i] = this.buffer[i]; |
| 254 | } | |
| 255 | // Return the buffer | |
| 256 | 0 | return newBuffer; |
| 257 | } | |
| 258 | } else { | |
| 259 | 0 | return convertArraytoUtf8BinaryString(this.buffer, 0, this.position); |
| 260 | } | |
| 261 | } | |
| 262 | }; | |
| 263 | ||
| 264 | /** | |
| 265 | * Length. | |
| 266 | * | |
| 267 | * @return {Number} the length of the binary. | |
| 268 | * @api public | |
| 269 | */ | |
| 270 | 1 | Binary.prototype.length = function length() { |
| 271 | 0 | return this.position; |
| 272 | }; | |
| 273 | ||
| 274 | /** | |
| 275 | * @ignore | |
| 276 | * @api private | |
| 277 | */ | |
| 278 | 1 | Binary.prototype.toJSON = function() { |
| 279 | 0 | return this.buffer != null ? this.buffer.toString('base64') : ''; |
| 280 | } | |
| 281 | ||
| 282 | /** | |
| 283 | * @ignore | |
| 284 | * @api private | |
| 285 | */ | |
| 286 | 1 | Binary.prototype.toString = function(format) { |
| 287 | 0 | return this.buffer != null ? this.buffer.slice(0, this.position).toString(format) : ''; |
| 288 | } | |
| 289 | ||
| 290 | 1 | Binary.BUFFER_SIZE = 256; |
| 291 | ||
| 292 | /** | |
| 293 | * Default BSON type | |
| 294 | * | |
| 295 | * @classconstant SUBTYPE_DEFAULT | |
| 296 | **/ | |
| 297 | 1 | Binary.SUBTYPE_DEFAULT = 0; |
| 298 | /** | |
| 299 | * Function BSON type | |
| 300 | * | |
| 301 | * @classconstant SUBTYPE_DEFAULT | |
| 302 | **/ | |
| 303 | 1 | Binary.SUBTYPE_FUNCTION = 1; |
| 304 | /** | |
| 305 | * Byte Array BSON type | |
| 306 | * | |
| 307 | * @classconstant SUBTYPE_DEFAULT | |
| 308 | **/ | |
| 309 | 1 | Binary.SUBTYPE_BYTE_ARRAY = 2; |
| 310 | /** | |
| 311 | * OLD UUID BSON type | |
| 312 | * | |
| 313 | * @classconstant SUBTYPE_DEFAULT | |
| 314 | **/ | |
| 315 | 1 | Binary.SUBTYPE_UUID_OLD = 3; |
| 316 | /** | |
| 317 | * UUID BSON type | |
| 318 | * | |
| 319 | * @classconstant SUBTYPE_DEFAULT | |
| 320 | **/ | |
| 321 | 1 | Binary.SUBTYPE_UUID = 4; |
| 322 | /** | |
| 323 | * MD5 BSON type | |
| 324 | * | |
| 325 | * @classconstant SUBTYPE_DEFAULT | |
| 326 | **/ | |
| 327 | 1 | Binary.SUBTYPE_MD5 = 5; |
| 328 | /** | |
| 329 | * User BSON type | |
| 330 | * | |
| 331 | * @classconstant SUBTYPE_DEFAULT | |
| 332 | **/ | |
| 333 | 1 | Binary.SUBTYPE_USER_DEFINED = 128; |
| 334 | ||
| 335 | /** | |
| 336 | * Expose. | |
| 337 | */ | |
| 338 | 1 | exports.Binary = Binary; |
| 339 | ||
| 340 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Binary Parser. | |
| 3 | * Jonas Raoni Soares Silva | |
| 4 | * http://jsfromhell.com/classes/binary-parser [v1.0] | |
| 5 | */ | |
| 6 | 1 | var chr = String.fromCharCode; |
| 7 | ||
| 8 | 1 | var maxBits = []; |
| 9 | 1 | for (var i = 0; i < 64; i++) { |
| 10 | 64 | maxBits[i] = Math.pow(2, i); |
| 11 | } | |
| 12 | ||
| 13 | 1 | function BinaryParser (bigEndian, allowExceptions) { |
| 14 | 0 | if(!(this instanceof BinaryParser)) return new BinaryParser(bigEndian, allowExceptions); |
| 15 | ||
| 16 | 0 | this.bigEndian = bigEndian; |
| 17 | 0 | this.allowExceptions = allowExceptions; |
| 18 | }; | |
| 19 | ||
| 20 | 1 | BinaryParser.warn = function warn (msg) { |
| 21 | 0 | if (this.allowExceptions) { |
| 22 | 0 | throw new Error(msg); |
| 23 | } | |
| 24 | ||
| 25 | 0 | return 1; |
| 26 | }; | |
| 27 | ||
| 28 | 1 | BinaryParser.decodeFloat = function decodeFloat (data, precisionBits, exponentBits) { |
| 29 | 0 | var b = new this.Buffer(this.bigEndian, data); |
| 30 | ||
| 31 | 0 | b.checkBuffer(precisionBits + exponentBits + 1); |
| 32 | ||
| 33 | 0 | var bias = maxBits[exponentBits - 1] - 1 |
| 34 | , signal = b.readBits(precisionBits + exponentBits, 1) | |
| 35 | , exponent = b.readBits(precisionBits, exponentBits) | |
| 36 | , significand = 0 | |
| 37 | , divisor = 2 | |
| 38 | , curByte = b.buffer.length + (-precisionBits >> 3) - 1; | |
| 39 | ||
| 40 | 0 | do { |
| 41 | 0 | for (var byteValue = b.buffer[ ++curByte ], startBit = precisionBits % 8 || 8, mask = 1 << startBit; mask >>= 1; ( byteValue & mask ) && ( significand += 1 / divisor ), divisor *= 2 ); |
| 42 | } while (precisionBits -= startBit); | |
| 43 | ||
| 44 | 0 | return exponent == ( bias << 1 ) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity : ( 1 + signal * -2 ) * ( exponent || significand ? !exponent ? Math.pow( 2, -bias + 1 ) * significand : Math.pow( 2, exponent - bias ) * ( 1 + significand ) : 0 ); |
| 45 | }; | |
| 46 | ||
| 47 | 1 | BinaryParser.decodeInt = function decodeInt (data, bits, signed, forceBigEndian) { |
| 48 | 0 | var b = new this.Buffer(this.bigEndian || forceBigEndian, data) |
| 49 | , x = b.readBits(0, bits) | |
| 50 | , max = maxBits[bits]; //max = Math.pow( 2, bits ); | |
| 51 | ||
| 52 | 0 | return signed && x >= max / 2 |
| 53 | ? x - max | |
| 54 | : x; | |
| 55 | }; | |
| 56 | ||
| 57 | 1 | BinaryParser.encodeFloat = function encodeFloat (data, precisionBits, exponentBits) { |
| 58 | 0 | var bias = maxBits[exponentBits - 1] - 1 |
| 59 | , minExp = -bias + 1 | |
| 60 | , maxExp = bias | |
| 61 | , minUnnormExp = minExp - precisionBits | |
| 62 | , n = parseFloat(data) | |
| 63 | , status = isNaN(n) || n == -Infinity || n == +Infinity ? n : 0 | |
| 64 | , exp = 0 | |
| 65 | , len = 2 * bias + 1 + precisionBits + 3 | |
| 66 | , bin = new Array(len) | |
| 67 | , signal = (n = status !== 0 ? 0 : n) < 0 | |
| 68 | , intPart = Math.floor(n = Math.abs(n)) | |
| 69 | , floatPart = n - intPart | |
| 70 | , lastBit | |
| 71 | , rounded | |
| 72 | , result | |
| 73 | , i | |
| 74 | , j; | |
| 75 | ||
| 76 | 0 | for (i = len; i; bin[--i] = 0); |
| 77 | ||
| 78 | 0 | for (i = bias + 2; intPart && i; bin[--i] = intPart % 2, intPart = Math.floor(intPart / 2)); |
| 79 | ||
| 80 | 0 | for (i = bias + 1; floatPart > 0 && i; (bin[++i] = ((floatPart *= 2) >= 1) - 0 ) && --floatPart); |
| 81 | ||
| 82 | 0 | for (i = -1; ++i < len && !bin[i];); |
| 83 | ||
| 84 | 0 | if (bin[(lastBit = precisionBits - 1 + (i = (exp = bias + 1 - i) >= minExp && exp <= maxExp ? i + 1 : bias + 1 - (exp = minExp - 1))) + 1]) { |
| 85 | 0 | if (!(rounded = bin[lastBit])) { |
| 86 | 0 | for (j = lastBit + 2; !rounded && j < len; rounded = bin[j++]); |
| 87 | } | |
| 88 | ||
| 89 | 0 | for (j = lastBit + 1; rounded && --j >= 0; (bin[j] = !bin[j] - 0) && (rounded = 0)); |
| 90 | } | |
| 91 | ||
| 92 | 0 | for (i = i - 2 < 0 ? -1 : i - 3; ++i < len && !bin[i];); |
| 93 | ||
| 94 | 0 | if ((exp = bias + 1 - i) >= minExp && exp <= maxExp) { |
| 95 | 0 | ++i; |
| 96 | 0 | } else if (exp < minExp) { |
| 97 | 0 | exp != bias + 1 - len && exp < minUnnormExp && this.warn("encodeFloat::float underflow"); |
| 98 | 0 | i = bias + 1 - (exp = minExp - 1); |
| 99 | } | |
| 100 | ||
| 101 | 0 | if (intPart || status !== 0) { |
| 102 | 0 | this.warn(intPart ? "encodeFloat::float overflow" : "encodeFloat::" + status); |
| 103 | 0 | exp = maxExp + 1; |
| 104 | 0 | i = bias + 2; |
| 105 | ||
| 106 | 0 | if (status == -Infinity) { |
| 107 | 0 | signal = 1; |
| 108 | 0 | } else if (isNaN(status)) { |
| 109 | 0 | bin[i] = 1; |
| 110 | } | |
| 111 | } | |
| 112 | ||
| 113 | 0 | for (n = Math.abs(exp + bias), j = exponentBits + 1, result = ""; --j; result = (n % 2) + result, n = n >>= 1); |
| 114 | ||
| 115 | 0 | for (n = 0, j = 0, i = (result = (signal ? "1" : "0") + result + bin.slice(i, i + precisionBits).join("")).length, r = []; i; j = (j + 1) % 8) { |
| 116 | 0 | n += (1 << j) * result.charAt(--i); |
| 117 | 0 | if (j == 7) { |
| 118 | 0 | r[r.length] = String.fromCharCode(n); |
| 119 | 0 | n = 0; |
| 120 | } | |
| 121 | } | |
| 122 | ||
| 123 | 0 | r[r.length] = n |
| 124 | ? String.fromCharCode(n) | |
| 125 | : ""; | |
| 126 | ||
| 127 | 0 | return (this.bigEndian ? r.reverse() : r).join(""); |
| 128 | }; | |
| 129 | ||
| 130 | 1 | BinaryParser.encodeInt = function encodeInt (data, bits, signed, forceBigEndian) { |
| 131 | 0 | var max = maxBits[bits]; |
| 132 | ||
| 133 | 0 | if (data >= max || data < -(max / 2)) { |
| 134 | 0 | this.warn("encodeInt::overflow"); |
| 135 | 0 | data = 0; |
| 136 | } | |
| 137 | ||
| 138 | 0 | if (data < 0) { |
| 139 | 0 | data += max; |
| 140 | } | |
| 141 | ||
| 142 | 0 | for (var r = []; data; r[r.length] = String.fromCharCode(data % 256), data = Math.floor(data / 256)); |
| 143 | ||
| 144 | 0 | for (bits = -(-bits >> 3) - r.length; bits--; r[r.length] = "\0"); |
| 145 | ||
| 146 | 0 | return ((this.bigEndian || forceBigEndian) ? r.reverse() : r).join(""); |
| 147 | }; | |
| 148 | ||
| 149 | 1 | BinaryParser.toSmall = function( data ){ return this.decodeInt( data, 8, true ); }; |
| 150 | 1 | BinaryParser.fromSmall = function( data ){ return this.encodeInt( data, 8, true ); }; |
| 151 | 1 | BinaryParser.toByte = function( data ){ return this.decodeInt( data, 8, false ); }; |
| 152 | 1 | BinaryParser.fromByte = function( data ){ return this.encodeInt( data, 8, false ); }; |
| 153 | 1 | BinaryParser.toShort = function( data ){ return this.decodeInt( data, 16, true ); }; |
| 154 | 1 | BinaryParser.fromShort = function( data ){ return this.encodeInt( data, 16, true ); }; |
| 155 | 1 | BinaryParser.toWord = function( data ){ return this.decodeInt( data, 16, false ); }; |
| 156 | 1 | BinaryParser.fromWord = function( data ){ return this.encodeInt( data, 16, false ); }; |
| 157 | 1 | BinaryParser.toInt = function( data ){ return this.decodeInt( data, 32, true ); }; |
| 158 | 1 | BinaryParser.fromInt = function( data ){ return this.encodeInt( data, 32, true ); }; |
| 159 | 1 | BinaryParser.toLong = function( data ){ return this.decodeInt( data, 64, true ); }; |
| 160 | 1 | BinaryParser.fromLong = function( data ){ return this.encodeInt( data, 64, true ); }; |
| 161 | 1 | BinaryParser.toDWord = function( data ){ return this.decodeInt( data, 32, false ); }; |
| 162 | 1 | BinaryParser.fromDWord = function( data ){ return this.encodeInt( data, 32, false ); }; |
| 163 | 1 | BinaryParser.toQWord = function( data ){ return this.decodeInt( data, 64, true ); }; |
| 164 | 1 | BinaryParser.fromQWord = function( data ){ return this.encodeInt( data, 64, true ); }; |
| 165 | 1 | BinaryParser.toFloat = function( data ){ return this.decodeFloat( data, 23, 8 ); }; |
| 166 | 1 | BinaryParser.fromFloat = function( data ){ return this.encodeFloat( data, 23, 8 ); }; |
| 167 | 1 | BinaryParser.toDouble = function( data ){ return this.decodeFloat( data, 52, 11 ); }; |
| 168 | 1 | BinaryParser.fromDouble = function( data ){ return this.encodeFloat( data, 52, 11 ); }; |
| 169 | ||
| 170 | // Factor out the encode so it can be shared by add_header and push_int32 | |
| 171 | 1 | BinaryParser.encode_int32 = function encode_int32 (number, asArray) { |
| 172 | 0 | var a, b, c, d, unsigned; |
| 173 | 0 | unsigned = (number < 0) ? (number + 0x100000000) : number; |
| 174 | 0 | a = Math.floor(unsigned / 0xffffff); |
| 175 | 0 | unsigned &= 0xffffff; |
| 176 | 0 | b = Math.floor(unsigned / 0xffff); |
| 177 | 0 | unsigned &= 0xffff; |
| 178 | 0 | c = Math.floor(unsigned / 0xff); |
| 179 | 0 | unsigned &= 0xff; |
| 180 | 0 | d = Math.floor(unsigned); |
| 181 | 0 | return asArray ? [chr(a), chr(b), chr(c), chr(d)] : chr(a) + chr(b) + chr(c) + chr(d); |
| 182 | }; | |
| 183 | ||
| 184 | 1 | BinaryParser.encode_int64 = function encode_int64 (number) { |
| 185 | 0 | var a, b, c, d, e, f, g, h, unsigned; |
| 186 | 0 | unsigned = (number < 0) ? (number + 0x10000000000000000) : number; |
| 187 | 0 | a = Math.floor(unsigned / 0xffffffffffffff); |
| 188 | 0 | unsigned &= 0xffffffffffffff; |
| 189 | 0 | b = Math.floor(unsigned / 0xffffffffffff); |
| 190 | 0 | unsigned &= 0xffffffffffff; |
| 191 | 0 | c = Math.floor(unsigned / 0xffffffffff); |
| 192 | 0 | unsigned &= 0xffffffffff; |
| 193 | 0 | d = Math.floor(unsigned / 0xffffffff); |
| 194 | 0 | unsigned &= 0xffffffff; |
| 195 | 0 | e = Math.floor(unsigned / 0xffffff); |
| 196 | 0 | unsigned &= 0xffffff; |
| 197 | 0 | f = Math.floor(unsigned / 0xffff); |
| 198 | 0 | unsigned &= 0xffff; |
| 199 | 0 | g = Math.floor(unsigned / 0xff); |
| 200 | 0 | unsigned &= 0xff; |
| 201 | 0 | h = Math.floor(unsigned); |
| 202 | 0 | return chr(a) + chr(b) + chr(c) + chr(d) + chr(e) + chr(f) + chr(g) + chr(h); |
| 203 | }; | |
| 204 | ||
| 205 | /** | |
| 206 | * UTF8 methods | |
| 207 | */ | |
| 208 | ||
| 209 | // Take a raw binary string and return a utf8 string | |
| 210 | 1 | BinaryParser.decode_utf8 = function decode_utf8 (binaryStr) { |
| 211 | 0 | var len = binaryStr.length |
| 212 | , decoded = '' | |
| 213 | , i = 0 | |
| 214 | , c = 0 | |
| 215 | , c1 = 0 | |
| 216 | , c2 = 0 | |
| 217 | , c3; | |
| 218 | ||
| 219 | 0 | while (i < len) { |
| 220 | 0 | c = binaryStr.charCodeAt(i); |
| 221 | 0 | if (c < 128) { |
| 222 | 0 | decoded += String.fromCharCode(c); |
| 223 | 0 | i++; |
| 224 | 0 | } else if ((c > 191) && (c < 224)) { |
| 225 | 0 | c2 = binaryStr.charCodeAt(i+1); |
| 226 | 0 | decoded += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); |
| 227 | 0 | i += 2; |
| 228 | } else { | |
| 229 | 0 | c2 = binaryStr.charCodeAt(i+1); |
| 230 | 0 | c3 = binaryStr.charCodeAt(i+2); |
| 231 | 0 | decoded += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); |
| 232 | 0 | i += 3; |
| 233 | } | |
| 234 | } | |
| 235 | ||
| 236 | 0 | return decoded; |
| 237 | }; | |
| 238 | ||
| 239 | // Encode a cstring | |
| 240 | 1 | BinaryParser.encode_cstring = function encode_cstring (s) { |
| 241 | 0 | return unescape(encodeURIComponent(s)) + BinaryParser.fromByte(0); |
| 242 | }; | |
| 243 | ||
| 244 | // Take a utf8 string and return a binary string | |
| 245 | 1 | BinaryParser.encode_utf8 = function encode_utf8 (s) { |
| 246 | 0 | var a = "" |
| 247 | , c; | |
| 248 | ||
| 249 | 0 | for (var n = 0, len = s.length; n < len; n++) { |
| 250 | 0 | c = s.charCodeAt(n); |
| 251 | ||
| 252 | 0 | if (c < 128) { |
| 253 | 0 | a += String.fromCharCode(c); |
| 254 | 0 | } else if ((c > 127) && (c < 2048)) { |
| 255 | 0 | a += String.fromCharCode((c>>6) | 192) ; |
| 256 | 0 | a += String.fromCharCode((c&63) | 128); |
| 257 | } else { | |
| 258 | 0 | a += String.fromCharCode((c>>12) | 224); |
| 259 | 0 | a += String.fromCharCode(((c>>6) & 63) | 128); |
| 260 | 0 | a += String.fromCharCode((c&63) | 128); |
| 261 | } | |
| 262 | } | |
| 263 | ||
| 264 | 0 | return a; |
| 265 | }; | |
| 266 | ||
| 267 | 1 | BinaryParser.hprint = function hprint (s) { |
| 268 | 0 | var number; |
| 269 | ||
| 270 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 271 | 0 | if (s.charCodeAt(i) < 32) { |
| 272 | 0 | number = s.charCodeAt(i) <= 15 |
| 273 | ? "0" + s.charCodeAt(i).toString(16) | |
| 274 | : s.charCodeAt(i).toString(16); | |
| 275 | 0 | process.stdout.write(number + " ") |
| 276 | } else { | |
| 277 | 0 | number = s.charCodeAt(i) <= 15 |
| 278 | ? "0" + s.charCodeAt(i).toString(16) | |
| 279 | : s.charCodeAt(i).toString(16); | |
| 280 | 0 | process.stdout.write(number + " ") |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | 0 | process.stdout.write("\n\n"); |
| 285 | }; | |
| 286 | ||
| 287 | 1 | BinaryParser.ilprint = function hprint (s) { |
| 288 | 0 | var number; |
| 289 | ||
| 290 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 291 | 0 | if (s.charCodeAt(i) < 32) { |
| 292 | 0 | number = s.charCodeAt(i) <= 15 |
| 293 | ? "0" + s.charCodeAt(i).toString(10) | |
| 294 | : s.charCodeAt(i).toString(10); | |
| 295 | ||
| 296 | 0 | require('util').debug(number+' : '); |
| 297 | } else { | |
| 298 | 0 | number = s.charCodeAt(i) <= 15 |
| 299 | ? "0" + s.charCodeAt(i).toString(10) | |
| 300 | : s.charCodeAt(i).toString(10); | |
| 301 | 0 | require('util').debug(number+' : '+ s.charAt(i)); |
| 302 | } | |
| 303 | } | |
| 304 | }; | |
| 305 | ||
| 306 | 1 | BinaryParser.hlprint = function hprint (s) { |
| 307 | 0 | var number; |
| 308 | ||
| 309 | 0 | for (var i = 0, len = s.length; i < len; i++) { |
| 310 | 0 | if (s.charCodeAt(i) < 32) { |
| 311 | 0 | number = s.charCodeAt(i) <= 15 |
| 312 | ? "0" + s.charCodeAt(i).toString(16) | |
| 313 | : s.charCodeAt(i).toString(16); | |
| 314 | 0 | require('util').debug(number+' : '); |
| 315 | } else { | |
| 316 | 0 | number = s.charCodeAt(i) <= 15 |
| 317 | ? "0" + s.charCodeAt(i).toString(16) | |
| 318 | : s.charCodeAt(i).toString(16); | |
| 319 | 0 | require('util').debug(number+' : '+ s.charAt(i)); |
| 320 | } | |
| 321 | } | |
| 322 | }; | |
| 323 | ||
| 324 | /** | |
| 325 | * BinaryParser buffer constructor. | |
| 326 | */ | |
| 327 | 1 | function BinaryParserBuffer (bigEndian, buffer) { |
| 328 | 0 | this.bigEndian = bigEndian || 0; |
| 329 | 0 | this.buffer = []; |
| 330 | 0 | this.setBuffer(buffer); |
| 331 | }; | |
| 332 | ||
| 333 | 1 | BinaryParserBuffer.prototype.setBuffer = function setBuffer (data) { |
| 334 | 0 | var l, i, b; |
| 335 | ||
| 336 | 0 | if (data) { |
| 337 | 0 | i = l = data.length; |
| 338 | 0 | b = this.buffer = new Array(l); |
| 339 | 0 | for (; i; b[l - i] = data.charCodeAt(--i)); |
| 340 | 0 | this.bigEndian && b.reverse(); |
| 341 | } | |
| 342 | }; | |
| 343 | ||
| 344 | 1 | BinaryParserBuffer.prototype.hasNeededBits = function hasNeededBits (neededBits) { |
| 345 | 0 | return this.buffer.length >= -(-neededBits >> 3); |
| 346 | }; | |
| 347 | ||
| 348 | 1 | BinaryParserBuffer.prototype.checkBuffer = function checkBuffer (neededBits) { |
| 349 | 0 | if (!this.hasNeededBits(neededBits)) { |
| 350 | 0 | throw new Error("checkBuffer::missing bytes"); |
| 351 | } | |
| 352 | }; | |
| 353 | ||
| 354 | 1 | BinaryParserBuffer.prototype.readBits = function readBits (start, length) { |
| 355 | //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) | |
| 356 | ||
| 357 | 0 | function shl (a, b) { |
| 358 | 0 | for (; b--; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); |
| 359 | 0 | return a; |
| 360 | } | |
| 361 | ||
| 362 | 0 | if (start < 0 || length <= 0) { |
| 363 | 0 | return 0; |
| 364 | } | |
| 365 | ||
| 366 | 0 | this.checkBuffer(start + length); |
| 367 | ||
| 368 | 0 | var offsetLeft |
| 369 | , offsetRight = start % 8 | |
| 370 | , curByte = this.buffer.length - ( start >> 3 ) - 1 | |
| 371 | , lastByte = this.buffer.length + ( -( start + length ) >> 3 ) | |
| 372 | , diff = curByte - lastByte | |
| 373 | , sum = ((this.buffer[ curByte ] >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1)) + (diff && (offsetLeft = (start + length) % 8) ? (this.buffer[lastByte++] & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight : 0); | |
| 374 | ||
| 375 | 0 | for(; diff; sum += shl(this.buffer[lastByte++], (diff-- << 3) - offsetRight)); |
| 376 | ||
| 377 | 0 | return sum; |
| 378 | }; | |
| 379 | ||
| 380 | /** | |
| 381 | * Expose. | |
| 382 | */ | |
| 383 | 1 | BinaryParser.Buffer = BinaryParserBuffer; |
| 384 | ||
| 385 | 1 | exports.BinaryParser = BinaryParser; |
| 386 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Long = require('./long').Long |
| 2 | , Double = require('./double').Double | |
| 3 | , Timestamp = require('./timestamp').Timestamp | |
| 4 | , ObjectID = require('./objectid').ObjectID | |
| 5 | , Symbol = require('./symbol').Symbol | |
| 6 | , Code = require('./code').Code | |
| 7 | , MinKey = require('./min_key').MinKey | |
| 8 | , MaxKey = require('./max_key').MaxKey | |
| 9 | , DBRef = require('./db_ref').DBRef | |
| 10 | , Binary = require('./binary').Binary | |
| 11 | , BinaryParser = require('./binary_parser').BinaryParser | |
| 12 | , writeIEEE754 = require('./float_parser').writeIEEE754 | |
| 13 | , readIEEE754 = require('./float_parser').readIEEE754 | |
| 14 | ||
| 15 | // To ensure that 0.4 of node works correctly | |
| 16 | 1 | var isDate = function isDate(d) { |
| 17 | 0 | return typeof d === 'object' && Object.prototype.toString.call(d) === '[object Date]'; |
| 18 | } | |
| 19 | ||
| 20 | /** | |
| 21 | * Create a new BSON instance | |
| 22 | * | |
| 23 | * @class Represents the BSON Parser | |
| 24 | * @return {BSON} instance of BSON Parser. | |
| 25 | */ | |
| 26 | 1 | function BSON () {}; |
| 27 | ||
| 28 | /** | |
| 29 | * @ignore | |
| 30 | * @api private | |
| 31 | */ | |
| 32 | // BSON MAX VALUES | |
| 33 | 1 | BSON.BSON_INT32_MAX = 0x7FFFFFFF; |
| 34 | 1 | BSON.BSON_INT32_MIN = -0x80000000; |
| 35 | ||
| 36 | 1 | BSON.BSON_INT64_MAX = Math.pow(2, 63) - 1; |
| 37 | 1 | BSON.BSON_INT64_MIN = -Math.pow(2, 63); |
| 38 | ||
| 39 | // JS MAX PRECISE VALUES | |
| 40 | 1 | BSON.JS_INT_MAX = 0x20000000000000; // Any integer up to 2^53 can be precisely represented by a double. |
| 41 | 1 | BSON.JS_INT_MIN = -0x20000000000000; // Any integer down to -2^53 can be precisely represented by a double. |
| 42 | ||
| 43 | // Internal long versions | |
| 44 | 1 | var JS_INT_MAX_LONG = Long.fromNumber(0x20000000000000); // Any integer up to 2^53 can be precisely represented by a double. |
| 45 | 1 | var JS_INT_MIN_LONG = Long.fromNumber(-0x20000000000000); // Any integer down to -2^53 can be precisely represented by a double. |
| 46 | ||
| 47 | /** | |
| 48 | * Number BSON Type | |
| 49 | * | |
| 50 | * @classconstant BSON_DATA_NUMBER | |
| 51 | **/ | |
| 52 | 1 | BSON.BSON_DATA_NUMBER = 1; |
| 53 | /** | |
| 54 | * String BSON Type | |
| 55 | * | |
| 56 | * @classconstant BSON_DATA_STRING | |
| 57 | **/ | |
| 58 | 1 | BSON.BSON_DATA_STRING = 2; |
| 59 | /** | |
| 60 | * Object BSON Type | |
| 61 | * | |
| 62 | * @classconstant BSON_DATA_OBJECT | |
| 63 | **/ | |
| 64 | 1 | BSON.BSON_DATA_OBJECT = 3; |
| 65 | /** | |
| 66 | * Array BSON Type | |
| 67 | * | |
| 68 | * @classconstant BSON_DATA_ARRAY | |
| 69 | **/ | |
| 70 | 1 | BSON.BSON_DATA_ARRAY = 4; |
| 71 | /** | |
| 72 | * Binary BSON Type | |
| 73 | * | |
| 74 | * @classconstant BSON_DATA_BINARY | |
| 75 | **/ | |
| 76 | 1 | BSON.BSON_DATA_BINARY = 5; |
| 77 | /** | |
| 78 | * ObjectID BSON Type | |
| 79 | * | |
| 80 | * @classconstant BSON_DATA_OID | |
| 81 | **/ | |
| 82 | 1 | BSON.BSON_DATA_OID = 7; |
| 83 | /** | |
| 84 | * Boolean BSON Type | |
| 85 | * | |
| 86 | * @classconstant BSON_DATA_BOOLEAN | |
| 87 | **/ | |
| 88 | 1 | BSON.BSON_DATA_BOOLEAN = 8; |
| 89 | /** | |
| 90 | * Date BSON Type | |
| 91 | * | |
| 92 | * @classconstant BSON_DATA_DATE | |
| 93 | **/ | |
| 94 | 1 | BSON.BSON_DATA_DATE = 9; |
| 95 | /** | |
| 96 | * null BSON Type | |
| 97 | * | |
| 98 | * @classconstant BSON_DATA_NULL | |
| 99 | **/ | |
| 100 | 1 | BSON.BSON_DATA_NULL = 10; |
| 101 | /** | |
| 102 | * RegExp BSON Type | |
| 103 | * | |
| 104 | * @classconstant BSON_DATA_REGEXP | |
| 105 | **/ | |
| 106 | 1 | BSON.BSON_DATA_REGEXP = 11; |
| 107 | /** | |
| 108 | * Code BSON Type | |
| 109 | * | |
| 110 | * @classconstant BSON_DATA_CODE | |
| 111 | **/ | |
| 112 | 1 | BSON.BSON_DATA_CODE = 13; |
| 113 | /** | |
| 114 | * Symbol BSON Type | |
| 115 | * | |
| 116 | * @classconstant BSON_DATA_SYMBOL | |
| 117 | **/ | |
| 118 | 1 | BSON.BSON_DATA_SYMBOL = 14; |
| 119 | /** | |
| 120 | * Code with Scope BSON Type | |
| 121 | * | |
| 122 | * @classconstant BSON_DATA_CODE_W_SCOPE | |
| 123 | **/ | |
| 124 | 1 | BSON.BSON_DATA_CODE_W_SCOPE = 15; |
| 125 | /** | |
| 126 | * 32 bit Integer BSON Type | |
| 127 | * | |
| 128 | * @classconstant BSON_DATA_INT | |
| 129 | **/ | |
| 130 | 1 | BSON.BSON_DATA_INT = 16; |
| 131 | /** | |
| 132 | * Timestamp BSON Type | |
| 133 | * | |
| 134 | * @classconstant BSON_DATA_TIMESTAMP | |
| 135 | **/ | |
| 136 | 1 | BSON.BSON_DATA_TIMESTAMP = 17; |
| 137 | /** | |
| 138 | * Long BSON Type | |
| 139 | * | |
| 140 | * @classconstant BSON_DATA_LONG | |
| 141 | **/ | |
| 142 | 1 | BSON.BSON_DATA_LONG = 18; |
| 143 | /** | |
| 144 | * MinKey BSON Type | |
| 145 | * | |
| 146 | * @classconstant BSON_DATA_MIN_KEY | |
| 147 | **/ | |
| 148 | 1 | BSON.BSON_DATA_MIN_KEY = 0xff; |
| 149 | /** | |
| 150 | * MaxKey BSON Type | |
| 151 | * | |
| 152 | * @classconstant BSON_DATA_MAX_KEY | |
| 153 | **/ | |
| 154 | 1 | BSON.BSON_DATA_MAX_KEY = 0x7f; |
| 155 | ||
| 156 | /** | |
| 157 | * Binary Default Type | |
| 158 | * | |
| 159 | * @classconstant BSON_BINARY_SUBTYPE_DEFAULT | |
| 160 | **/ | |
| 161 | 1 | BSON.BSON_BINARY_SUBTYPE_DEFAULT = 0; |
| 162 | /** | |
| 163 | * Binary Function Type | |
| 164 | * | |
| 165 | * @classconstant BSON_BINARY_SUBTYPE_FUNCTION | |
| 166 | **/ | |
| 167 | 1 | BSON.BSON_BINARY_SUBTYPE_FUNCTION = 1; |
| 168 | /** | |
| 169 | * Binary Byte Array Type | |
| 170 | * | |
| 171 | * @classconstant BSON_BINARY_SUBTYPE_BYTE_ARRAY | |
| 172 | **/ | |
| 173 | 1 | BSON.BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2; |
| 174 | /** | |
| 175 | * Binary UUID Type | |
| 176 | * | |
| 177 | * @classconstant BSON_BINARY_SUBTYPE_UUID | |
| 178 | **/ | |
| 179 | 1 | BSON.BSON_BINARY_SUBTYPE_UUID = 3; |
| 180 | /** | |
| 181 | * Binary MD5 Type | |
| 182 | * | |
| 183 | * @classconstant BSON_BINARY_SUBTYPE_MD5 | |
| 184 | **/ | |
| 185 | 1 | BSON.BSON_BINARY_SUBTYPE_MD5 = 4; |
| 186 | /** | |
| 187 | * Binary User Defined Type | |
| 188 | * | |
| 189 | * @classconstant BSON_BINARY_SUBTYPE_USER_DEFINED | |
| 190 | **/ | |
| 191 | 1 | BSON.BSON_BINARY_SUBTYPE_USER_DEFINED = 128; |
| 192 | ||
| 193 | /** | |
| 194 | * Calculate the bson size for a passed in Javascript object. | |
| 195 | * | |
| 196 | * @param {Object} object the Javascript object to calculate the BSON byte size for. | |
| 197 | * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. | |
| 198 | * @return {Number} returns the number of bytes the BSON object will take up. | |
| 199 | * @api public | |
| 200 | */ | |
| 201 | 1 | BSON.calculateObjectSize = function calculateObjectSize(object, serializeFunctions) { |
| 202 | 0 | var totalLength = (4 + 1); |
| 203 | ||
| 204 | 0 | if(Array.isArray(object)) { |
| 205 | 0 | for(var i = 0; i < object.length; i++) { |
| 206 | 0 | totalLength += calculateElement(i.toString(), object[i], serializeFunctions) |
| 207 | } | |
| 208 | } else { | |
| 209 | // If we have toBSON defined, override the current object | |
| 210 | 0 | if(object.toBSON) { |
| 211 | 0 | object = object.toBSON(); |
| 212 | } | |
| 213 | ||
| 214 | // Calculate size | |
| 215 | 0 | for(var key in object) { |
| 216 | 0 | totalLength += calculateElement(key, object[key], serializeFunctions) |
| 217 | } | |
| 218 | } | |
| 219 | ||
| 220 | 0 | return totalLength; |
| 221 | } | |
| 222 | ||
| 223 | /** | |
| 224 | * @ignore | |
| 225 | * @api private | |
| 226 | */ | |
| 227 | 1 | function calculateElement(name, value, serializeFunctions) { |
| 228 | 0 | var isBuffer = typeof Buffer !== 'undefined'; |
| 229 | ||
| 230 | 0 | switch(typeof value) { |
| 231 | case 'string': | |
| 232 | 0 | return 1 + (!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1 + 4 + (!isBuffer ? numberOfBytes(value) : Buffer.byteLength(value, 'utf8')) + 1; |
| 233 | case 'number': | |
| 234 | 0 | if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 235 | 0 | if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { // 32 bit |
| 236 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (4 + 1); |
| 237 | } else { | |
| 238 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 239 | } | |
| 240 | } else { // 64 bit | |
| 241 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 242 | } | |
| 243 | case 'undefined': | |
| 244 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); |
| 245 | case 'boolean': | |
| 246 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 1); |
| 247 | case 'object': | |
| 248 | 0 | if(value == null || value instanceof MinKey || value instanceof MaxKey || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { |
| 249 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1); |
| 250 | 0 | } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { |
| 251 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (12 + 1); |
| 252 | 0 | } else if(value instanceof Date || isDate(value)) { |
| 253 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 254 | 0 | } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { |
| 255 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (1 + 4 + 1) + value.length; |
| 256 | 0 | } else if(value instanceof Long || value instanceof Double || value instanceof Timestamp |
| 257 | || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Double' || value['_bsontype'] == 'Timestamp') { | |
| 258 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (8 + 1); |
| 259 | 0 | } else if(value instanceof Code || value['_bsontype'] == 'Code') { |
| 260 | // Calculate size depending on the availability of a scope | |
| 261 | 0 | if(value.scope != null && Object.keys(value.scope).length > 0) { |
| 262 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 263 | } else { | |
| 264 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.code.toString()) : Buffer.byteLength(value.code.toString(), 'utf8')) + 1; |
| 265 | } | |
| 266 | 0 | } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { |
| 267 | // Check what kind of subtype we have | |
| 268 | 0 | if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) { |
| 269 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1 + 4); |
| 270 | } else { | |
| 271 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + (value.position + 1 + 4 + 1); |
| 272 | } | |
| 273 | 0 | } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { |
| 274 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + ((!isBuffer ? numberOfBytes(value.value) : Buffer.byteLength(value.value, 'utf8')) + 4 + 1 + 1); |
| 275 | 0 | } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { |
| 276 | // Set up correct object for serialization | |
| 277 | 0 | var ordered_values = { |
| 278 | '$ref': value.namespace | |
| 279 | , '$id' : value.oid | |
| 280 | }; | |
| 281 | ||
| 282 | // Add db reference if it exists | |
| 283 | 0 | if(null != value.db) { |
| 284 | 0 | ordered_values['$db'] = value.db; |
| 285 | } | |
| 286 | ||
| 287 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + BSON.calculateObjectSize(ordered_values, serializeFunctions); |
| 288 | 0 | } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { |
| 289 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 |
| 290 | + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 | |
| 291 | } else { | |
| 292 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + BSON.calculateObjectSize(value, serializeFunctions) + 1; |
| 293 | } | |
| 294 | case 'function': | |
| 295 | // WTF for 0.4.X where typeof /someregexp/ === 'function' | |
| 296 | 0 | if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { |
| 297 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + (!isBuffer ? numberOfBytes(value.source) : Buffer.byteLength(value.source, 'utf8')) + 1 |
| 298 | + (value.global ? 1 : 0) + (value.ignoreCase ? 1 : 0) + (value.multiline ? 1 : 0) + 1 | |
| 299 | } else { | |
| 300 | 0 | if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { |
| 301 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1 + BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 302 | 0 | } else if(serializeFunctions) { |
| 303 | 0 | return (name != null ? ((!isBuffer ? numberOfBytes(name) : Buffer.byteLength(name, 'utf8')) + 1) : 0) + 1 + 4 + (!isBuffer ? numberOfBytes(value.toString()) : Buffer.byteLength(value.toString(), 'utf8')) + 1; |
| 304 | } | |
| 305 | } | |
| 306 | } | |
| 307 | ||
| 308 | 0 | return 0; |
| 309 | } | |
| 310 | ||
| 311 | /** | |
| 312 | * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. | |
| 313 | * | |
| 314 | * @param {Object} object the Javascript object to serialize. | |
| 315 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 316 | * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. | |
| 317 | * @param {Number} index the index in the buffer where we wish to start serializing into. | |
| 318 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 319 | * @return {Number} returns the new write index in the Buffer. | |
| 320 | * @api public | |
| 321 | */ | |
| 322 | 1 | BSON.serializeWithBufferAndIndex = function serializeWithBufferAndIndex(object, checkKeys, buffer, index, serializeFunctions) { |
| 323 | // Default setting false | |
| 324 | 0 | serializeFunctions = serializeFunctions == null ? false : serializeFunctions; |
| 325 | // Write end information (length of the object) | |
| 326 | 0 | var size = buffer.length; |
| 327 | // Write the size of the object | |
| 328 | 0 | buffer[index++] = size & 0xff; |
| 329 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 330 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 331 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 332 | 0 | return serializeObject(object, checkKeys, buffer, index, serializeFunctions) - 1; |
| 333 | } | |
| 334 | ||
| 335 | /** | |
| 336 | * @ignore | |
| 337 | * @api private | |
| 338 | */ | |
| 339 | 1 | var serializeObject = function(object, checkKeys, buffer, index, serializeFunctions) { |
| 340 | // Process the object | |
| 341 | 0 | if(Array.isArray(object)) { |
| 342 | 0 | for(var i = 0; i < object.length; i++) { |
| 343 | 0 | index = packElement(i.toString(), object[i], checkKeys, buffer, index, serializeFunctions); |
| 344 | } | |
| 345 | } else { | |
| 346 | // If we have toBSON defined, override the current object | |
| 347 | 0 | if(object.toBSON) { |
| 348 | 0 | object = object.toBSON(); |
| 349 | } | |
| 350 | ||
| 351 | // Serialize the object | |
| 352 | 0 | for(var key in object) { |
| 353 | // Check the key and throw error if it's illegal | |
| 354 | 0 | if (key != '$db' && key != '$ref' && key != '$id') { |
| 355 | // dollars and dots ok | |
| 356 | 0 | BSON.checkKey(key, !checkKeys); |
| 357 | } | |
| 358 | ||
| 359 | // Pack the element | |
| 360 | 0 | index = packElement(key, object[key], checkKeys, buffer, index, serializeFunctions); |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | // Write zero | |
| 365 | 0 | buffer[index++] = 0; |
| 366 | 0 | return index; |
| 367 | } | |
| 368 | ||
| 369 | 1 | var stringToBytes = function(str) { |
| 370 | 0 | var ch, st, re = []; |
| 371 | 0 | for (var i = 0; i < str.length; i++ ) { |
| 372 | 0 | ch = str.charCodeAt(i); // get char |
| 373 | 0 | st = []; // set up "stack" |
| 374 | 0 | do { |
| 375 | 0 | st.push( ch & 0xFF ); // push byte to stack |
| 376 | 0 | ch = ch >> 8; // shift value down by 1 byte |
| 377 | } | |
| 378 | while ( ch ); | |
| 379 | // add stack contents to result | |
| 380 | // done because chars have "wrong" endianness | |
| 381 | 0 | re = re.concat( st.reverse() ); |
| 382 | } | |
| 383 | // return an array of bytes | |
| 384 | 0 | return re; |
| 385 | } | |
| 386 | ||
| 387 | 1 | var numberOfBytes = function(str) { |
| 388 | 0 | var ch, st, re = 0; |
| 389 | 0 | for (var i = 0; i < str.length; i++ ) { |
| 390 | 0 | ch = str.charCodeAt(i); // get char |
| 391 | 0 | st = []; // set up "stack" |
| 392 | 0 | do { |
| 393 | 0 | st.push( ch & 0xFF ); // push byte to stack |
| 394 | 0 | ch = ch >> 8; // shift value down by 1 byte |
| 395 | } | |
| 396 | while ( ch ); | |
| 397 | // add stack contents to result | |
| 398 | // done because chars have "wrong" endianness | |
| 399 | 0 | re = re + st.length; |
| 400 | } | |
| 401 | // return an array of bytes | |
| 402 | 0 | return re; |
| 403 | } | |
| 404 | ||
| 405 | /** | |
| 406 | * @ignore | |
| 407 | * @api private | |
| 408 | */ | |
| 409 | 1 | var writeToTypedArray = function(buffer, string, index) { |
| 410 | 0 | var bytes = stringToBytes(string); |
| 411 | 0 | for(var i = 0; i < bytes.length; i++) { |
| 412 | 0 | buffer[index + i] = bytes[i]; |
| 413 | } | |
| 414 | 0 | return bytes.length; |
| 415 | } | |
| 416 | ||
| 417 | /** | |
| 418 | * @ignore | |
| 419 | * @api private | |
| 420 | */ | |
| 421 | 1 | var supportsBuffer = typeof Buffer != 'undefined'; |
| 422 | ||
| 423 | /** | |
| 424 | * @ignore | |
| 425 | * @api private | |
| 426 | */ | |
| 427 | 1 | var packElement = function(name, value, checkKeys, buffer, index, serializeFunctions) { |
| 428 | 0 | var startIndex = index; |
| 429 | ||
| 430 | 0 | switch(typeof value) { |
| 431 | case 'string': | |
| 432 | // Encode String type | |
| 433 | 0 | buffer[index++] = BSON.BSON_DATA_STRING; |
| 434 | // Number of written bytes | |
| 435 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 436 | // Encode the name | |
| 437 | 0 | index = index + numberOfWrittenBytes + 1; |
| 438 | 0 | buffer[index - 1] = 0; |
| 439 | ||
| 440 | // Calculate size | |
| 441 | 0 | var size = supportsBuffer ? Buffer.byteLength(value) + 1 : numberOfBytes(value) + 1; |
| 442 | // Write the size of the string to buffer | |
| 443 | 0 | buffer[index + 3] = (size >> 24) & 0xff; |
| 444 | 0 | buffer[index + 2] = (size >> 16) & 0xff; |
| 445 | 0 | buffer[index + 1] = (size >> 8) & 0xff; |
| 446 | 0 | buffer[index] = size & 0xff; |
| 447 | // Ajust the index | |
| 448 | 0 | index = index + 4; |
| 449 | // Write the string | |
| 450 | 0 | supportsBuffer ? buffer.write(value, index, 'utf8') : writeToTypedArray(buffer, value, index); |
| 451 | // Update index | |
| 452 | 0 | index = index + size - 1; |
| 453 | // Write zero | |
| 454 | 0 | buffer[index++] = 0; |
| 455 | // Return index | |
| 456 | 0 | return index; |
| 457 | case 'number': | |
| 458 | // We have an integer value | |
| 459 | 0 | if(Math.floor(value) === value && value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 460 | // If the value fits in 32 bits encode as int, if it fits in a double | |
| 461 | // encode it as a double, otherwise long | |
| 462 | 0 | if(value >= BSON.BSON_INT32_MIN && value <= BSON.BSON_INT32_MAX) { |
| 463 | // Set int type 32 bits or less | |
| 464 | 0 | buffer[index++] = BSON.BSON_DATA_INT; |
| 465 | // Number of written bytes | |
| 466 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 467 | // Encode the name | |
| 468 | 0 | index = index + numberOfWrittenBytes + 1; |
| 469 | 0 | buffer[index - 1] = 0; |
| 470 | // Write the int value | |
| 471 | 0 | buffer[index++] = value & 0xff; |
| 472 | 0 | buffer[index++] = (value >> 8) & 0xff; |
| 473 | 0 | buffer[index++] = (value >> 16) & 0xff; |
| 474 | 0 | buffer[index++] = (value >> 24) & 0xff; |
| 475 | 0 | } else if(value >= BSON.JS_INT_MIN && value <= BSON.JS_INT_MAX) { |
| 476 | // Encode as double | |
| 477 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 478 | // Number of written bytes | |
| 479 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 480 | // Encode the name | |
| 481 | 0 | index = index + numberOfWrittenBytes + 1; |
| 482 | 0 | buffer[index - 1] = 0; |
| 483 | // Write float | |
| 484 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 485 | // Ajust index | |
| 486 | 0 | index = index + 8; |
| 487 | } else { | |
| 488 | // Set long type | |
| 489 | 0 | buffer[index++] = BSON.BSON_DATA_LONG; |
| 490 | // Number of written bytes | |
| 491 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 492 | // Encode the name | |
| 493 | 0 | index = index + numberOfWrittenBytes + 1; |
| 494 | 0 | buffer[index - 1] = 0; |
| 495 | 0 | var longVal = Long.fromNumber(value); |
| 496 | 0 | var lowBits = longVal.getLowBits(); |
| 497 | 0 | var highBits = longVal.getHighBits(); |
| 498 | // Encode low bits | |
| 499 | 0 | buffer[index++] = lowBits & 0xff; |
| 500 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 501 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 502 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 503 | // Encode high bits | |
| 504 | 0 | buffer[index++] = highBits & 0xff; |
| 505 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 506 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 507 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 508 | } | |
| 509 | } else { | |
| 510 | // Encode as double | |
| 511 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 512 | // Number of written bytes | |
| 513 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 514 | // Encode the name | |
| 515 | 0 | index = index + numberOfWrittenBytes + 1; |
| 516 | 0 | buffer[index - 1] = 0; |
| 517 | // Write float | |
| 518 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 519 | // Ajust index | |
| 520 | 0 | index = index + 8; |
| 521 | } | |
| 522 | ||
| 523 | 0 | return index; |
| 524 | case 'undefined': | |
| 525 | // Set long type | |
| 526 | 0 | buffer[index++] = BSON.BSON_DATA_NULL; |
| 527 | // Number of written bytes | |
| 528 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 529 | // Encode the name | |
| 530 | 0 | index = index + numberOfWrittenBytes + 1; |
| 531 | 0 | buffer[index - 1] = 0; |
| 532 | 0 | return index; |
| 533 | case 'boolean': | |
| 534 | // Write the type | |
| 535 | 0 | buffer[index++] = BSON.BSON_DATA_BOOLEAN; |
| 536 | // Number of written bytes | |
| 537 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 538 | // Encode the name | |
| 539 | 0 | index = index + numberOfWrittenBytes + 1; |
| 540 | 0 | buffer[index - 1] = 0; |
| 541 | // Encode the boolean value | |
| 542 | 0 | buffer[index++] = value ? 1 : 0; |
| 543 | 0 | return index; |
| 544 | case 'object': | |
| 545 | 0 | if(value === null || value instanceof MinKey || value instanceof MaxKey |
| 546 | || value['_bsontype'] == 'MinKey' || value['_bsontype'] == 'MaxKey') { | |
| 547 | // Write the type of either min or max key | |
| 548 | 0 | if(value === null) { |
| 549 | 0 | buffer[index++] = BSON.BSON_DATA_NULL; |
| 550 | 0 | } else if(value instanceof MinKey) { |
| 551 | 0 | buffer[index++] = BSON.BSON_DATA_MIN_KEY; |
| 552 | } else { | |
| 553 | 0 | buffer[index++] = BSON.BSON_DATA_MAX_KEY; |
| 554 | } | |
| 555 | ||
| 556 | // Number of written bytes | |
| 557 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 558 | // Encode the name | |
| 559 | 0 | index = index + numberOfWrittenBytes + 1; |
| 560 | 0 | buffer[index - 1] = 0; |
| 561 | 0 | return index; |
| 562 | 0 | } else if(value instanceof ObjectID || value['_bsontype'] == 'ObjectID') { |
| 563 | // Write the type | |
| 564 | 0 | buffer[index++] = BSON.BSON_DATA_OID; |
| 565 | // Number of written bytes | |
| 566 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 567 | // Encode the name | |
| 568 | 0 | index = index + numberOfWrittenBytes + 1; |
| 569 | 0 | buffer[index - 1] = 0; |
| 570 | ||
| 571 | // Write objectid | |
| 572 | 0 | supportsBuffer ? buffer.write(value.id, index, 'binary') : writeToTypedArray(buffer, value.id, index); |
| 573 | // Ajust index | |
| 574 | 0 | index = index + 12; |
| 575 | 0 | return index; |
| 576 | 0 | } else if(value instanceof Date || isDate(value)) { |
| 577 | // Write the type | |
| 578 | 0 | buffer[index++] = BSON.BSON_DATA_DATE; |
| 579 | // Number of written bytes | |
| 580 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 581 | // Encode the name | |
| 582 | 0 | index = index + numberOfWrittenBytes + 1; |
| 583 | 0 | buffer[index - 1] = 0; |
| 584 | ||
| 585 | // Write the date | |
| 586 | 0 | var dateInMilis = Long.fromNumber(value.getTime()); |
| 587 | 0 | var lowBits = dateInMilis.getLowBits(); |
| 588 | 0 | var highBits = dateInMilis.getHighBits(); |
| 589 | // Encode low bits | |
| 590 | 0 | buffer[index++] = lowBits & 0xff; |
| 591 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 592 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 593 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 594 | // Encode high bits | |
| 595 | 0 | buffer[index++] = highBits & 0xff; |
| 596 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 597 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 598 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 599 | 0 | return index; |
| 600 | 0 | } else if(typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { |
| 601 | // Write the type | |
| 602 | 0 | buffer[index++] = BSON.BSON_DATA_BINARY; |
| 603 | // Number of written bytes | |
| 604 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 605 | // Encode the name | |
| 606 | 0 | index = index + numberOfWrittenBytes + 1; |
| 607 | 0 | buffer[index - 1] = 0; |
| 608 | // Get size of the buffer (current write point) | |
| 609 | 0 | var size = value.length; |
| 610 | // Write the size of the string to buffer | |
| 611 | 0 | buffer[index++] = size & 0xff; |
| 612 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 613 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 614 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 615 | // Write the default subtype | |
| 616 | 0 | buffer[index++] = BSON.BSON_BINARY_SUBTYPE_DEFAULT; |
| 617 | // Copy the content form the binary field to the buffer | |
| 618 | 0 | value.copy(buffer, index, 0, size); |
| 619 | // Adjust the index | |
| 620 | 0 | index = index + size; |
| 621 | 0 | return index; |
| 622 | 0 | } else if(value instanceof Long || value instanceof Timestamp || value['_bsontype'] == 'Long' || value['_bsontype'] == 'Timestamp') { |
| 623 | // Write the type | |
| 624 | 0 | buffer[index++] = value instanceof Long ? BSON.BSON_DATA_LONG : BSON.BSON_DATA_TIMESTAMP; |
| 625 | // Number of written bytes | |
| 626 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 627 | // Encode the name | |
| 628 | 0 | index = index + numberOfWrittenBytes + 1; |
| 629 | 0 | buffer[index - 1] = 0; |
| 630 | // Write the date | |
| 631 | 0 | var lowBits = value.getLowBits(); |
| 632 | 0 | var highBits = value.getHighBits(); |
| 633 | // Encode low bits | |
| 634 | 0 | buffer[index++] = lowBits & 0xff; |
| 635 | 0 | buffer[index++] = (lowBits >> 8) & 0xff; |
| 636 | 0 | buffer[index++] = (lowBits >> 16) & 0xff; |
| 637 | 0 | buffer[index++] = (lowBits >> 24) & 0xff; |
| 638 | // Encode high bits | |
| 639 | 0 | buffer[index++] = highBits & 0xff; |
| 640 | 0 | buffer[index++] = (highBits >> 8) & 0xff; |
| 641 | 0 | buffer[index++] = (highBits >> 16) & 0xff; |
| 642 | 0 | buffer[index++] = (highBits >> 24) & 0xff; |
| 643 | 0 | return index; |
| 644 | 0 | } else if(value instanceof Double || value['_bsontype'] == 'Double') { |
| 645 | // Encode as double | |
| 646 | 0 | buffer[index++] = BSON.BSON_DATA_NUMBER; |
| 647 | // Number of written bytes | |
| 648 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 649 | // Encode the name | |
| 650 | 0 | index = index + numberOfWrittenBytes + 1; |
| 651 | 0 | buffer[index - 1] = 0; |
| 652 | // Write float | |
| 653 | 0 | writeIEEE754(buffer, value, index, 'little', 52, 8); |
| 654 | // Ajust index | |
| 655 | 0 | index = index + 8; |
| 656 | 0 | return index; |
| 657 | 0 | } else if(value instanceof Code || value['_bsontype'] == 'Code') { |
| 658 | 0 | if(value.scope != null && Object.keys(value.scope).length > 0) { |
| 659 | // Write the type | |
| 660 | 0 | buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; |
| 661 | // Number of written bytes | |
| 662 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 663 | // Encode the name | |
| 664 | 0 | index = index + numberOfWrittenBytes + 1; |
| 665 | 0 | buffer[index - 1] = 0; |
| 666 | // Calculate the scope size | |
| 667 | 0 | var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 668 | // Function string | |
| 669 | 0 | var functionString = value.code.toString(); |
| 670 | // Function Size | |
| 671 | 0 | var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 672 | ||
| 673 | // Calculate full size of the object | |
| 674 | 0 | var totalSize = 4 + codeSize + scopeSize + 4; |
| 675 | ||
| 676 | // Write the total size of the object | |
| 677 | 0 | buffer[index++] = totalSize & 0xff; |
| 678 | 0 | buffer[index++] = (totalSize >> 8) & 0xff; |
| 679 | 0 | buffer[index++] = (totalSize >> 16) & 0xff; |
| 680 | 0 | buffer[index++] = (totalSize >> 24) & 0xff; |
| 681 | ||
| 682 | // Write the size of the string to buffer | |
| 683 | 0 | buffer[index++] = codeSize & 0xff; |
| 684 | 0 | buffer[index++] = (codeSize >> 8) & 0xff; |
| 685 | 0 | buffer[index++] = (codeSize >> 16) & 0xff; |
| 686 | 0 | buffer[index++] = (codeSize >> 24) & 0xff; |
| 687 | ||
| 688 | // Write the string | |
| 689 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 690 | // Update index | |
| 691 | 0 | index = index + codeSize - 1; |
| 692 | // Write zero | |
| 693 | 0 | buffer[index++] = 0; |
| 694 | // Serialize the scope object | |
| 695 | 0 | var scopeObjectBuffer = supportsBuffer ? new Buffer(scopeSize) : new Uint8Array(new ArrayBuffer(scopeSize)); |
| 696 | // Execute the serialization into a seperate buffer | |
| 697 | 0 | serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); |
| 698 | ||
| 699 | // Adjusted scope Size (removing the header) | |
| 700 | 0 | var scopeDocSize = scopeSize; |
| 701 | // Write scope object size | |
| 702 | 0 | buffer[index++] = scopeDocSize & 0xff; |
| 703 | 0 | buffer[index++] = (scopeDocSize >> 8) & 0xff; |
| 704 | 0 | buffer[index++] = (scopeDocSize >> 16) & 0xff; |
| 705 | 0 | buffer[index++] = (scopeDocSize >> 24) & 0xff; |
| 706 | ||
| 707 | // Write the scopeObject into the buffer | |
| 708 | 0 | supportsBuffer ? scopeObjectBuffer.copy(buffer, index, 0, scopeSize) : buffer.set(scopeObjectBuffer, index); |
| 709 | // Adjust index, removing the empty size of the doc (5 bytes 0000000005) | |
| 710 | 0 | index = index + scopeDocSize - 5; |
| 711 | // Write trailing zero | |
| 712 | 0 | buffer[index++] = 0; |
| 713 | 0 | return index |
| 714 | } else { | |
| 715 | 0 | buffer[index++] = BSON.BSON_DATA_CODE; |
| 716 | // Number of written bytes | |
| 717 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 718 | // Encode the name | |
| 719 | 0 | index = index + numberOfWrittenBytes + 1; |
| 720 | 0 | buffer[index - 1] = 0; |
| 721 | // Function string | |
| 722 | 0 | var functionString = value.code.toString(); |
| 723 | // Function Size | |
| 724 | 0 | var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 725 | // Write the size of the string to buffer | |
| 726 | 0 | buffer[index++] = size & 0xff; |
| 727 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 728 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 729 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 730 | // Write the string | |
| 731 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 732 | // Update index | |
| 733 | 0 | index = index + size - 1; |
| 734 | // Write zero | |
| 735 | 0 | buffer[index++] = 0; |
| 736 | 0 | return index; |
| 737 | } | |
| 738 | 0 | } else if(value instanceof Binary || value['_bsontype'] == 'Binary') { |
| 739 | // Write the type | |
| 740 | 0 | buffer[index++] = BSON.BSON_DATA_BINARY; |
| 741 | // Number of written bytes | |
| 742 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 743 | // Encode the name | |
| 744 | 0 | index = index + numberOfWrittenBytes + 1; |
| 745 | 0 | buffer[index - 1] = 0; |
| 746 | // Extract the buffer | |
| 747 | 0 | var data = value.value(true); |
| 748 | // Calculate size | |
| 749 | 0 | var size = value.position; |
| 750 | // Write the size of the string to buffer | |
| 751 | 0 | buffer[index++] = size & 0xff; |
| 752 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 753 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 754 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 755 | // Write the subtype to the buffer | |
| 756 | 0 | buffer[index++] = value.sub_type; |
| 757 | ||
| 758 | // If we have binary type 2 the 4 first bytes are the size | |
| 759 | 0 | if(value.sub_type == Binary.SUBTYPE_BYTE_ARRAY) { |
| 760 | 0 | buffer[index++] = size & 0xff; |
| 761 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 762 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 763 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 764 | } | |
| 765 | ||
| 766 | // Write the data to the object | |
| 767 | 0 | supportsBuffer ? data.copy(buffer, index, 0, value.position) : buffer.set(data, index); |
| 768 | // Ajust index | |
| 769 | 0 | index = index + value.position; |
| 770 | 0 | return index; |
| 771 | 0 | } else if(value instanceof Symbol || value['_bsontype'] == 'Symbol') { |
| 772 | // Write the type | |
| 773 | 0 | buffer[index++] = BSON.BSON_DATA_SYMBOL; |
| 774 | // Number of written bytes | |
| 775 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 776 | // Encode the name | |
| 777 | 0 | index = index + numberOfWrittenBytes + 1; |
| 778 | 0 | buffer[index - 1] = 0; |
| 779 | // Calculate size | |
| 780 | 0 | var size = supportsBuffer ? Buffer.byteLength(value.value) + 1 : numberOfBytes(value.value) + 1; |
| 781 | // Write the size of the string to buffer | |
| 782 | 0 | buffer[index++] = size & 0xff; |
| 783 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 784 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 785 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 786 | // Write the string | |
| 787 | 0 | buffer.write(value.value, index, 'utf8'); |
| 788 | // Update index | |
| 789 | 0 | index = index + size - 1; |
| 790 | // Write zero | |
| 791 | 0 | buffer[index++] = 0x00; |
| 792 | 0 | return index; |
| 793 | 0 | } else if(value instanceof DBRef || value['_bsontype'] == 'DBRef') { |
| 794 | // Write the type | |
| 795 | 0 | buffer[index++] = BSON.BSON_DATA_OBJECT; |
| 796 | // Number of written bytes | |
| 797 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 798 | // Encode the name | |
| 799 | 0 | index = index + numberOfWrittenBytes + 1; |
| 800 | 0 | buffer[index - 1] = 0; |
| 801 | // Set up correct object for serialization | |
| 802 | 0 | var ordered_values = { |
| 803 | '$ref': value.namespace | |
| 804 | , '$id' : value.oid | |
| 805 | }; | |
| 806 | ||
| 807 | // Add db reference if it exists | |
| 808 | 0 | if(null != value.db) { |
| 809 | 0 | ordered_values['$db'] = value.db; |
| 810 | } | |
| 811 | ||
| 812 | // Message size | |
| 813 | 0 | var size = BSON.calculateObjectSize(ordered_values, serializeFunctions); |
| 814 | // Serialize the object | |
| 815 | 0 | var endIndex = BSON.serializeWithBufferAndIndex(ordered_values, checkKeys, buffer, index, serializeFunctions); |
| 816 | // Write the size of the string to buffer | |
| 817 | 0 | buffer[index++] = size & 0xff; |
| 818 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 819 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 820 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 821 | // Write zero for object | |
| 822 | 0 | buffer[endIndex++] = 0x00; |
| 823 | // Return the end index | |
| 824 | 0 | return endIndex; |
| 825 | 0 | } else if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]') { |
| 826 | // Write the type | |
| 827 | 0 | buffer[index++] = BSON.BSON_DATA_REGEXP; |
| 828 | // Number of written bytes | |
| 829 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 830 | // Encode the name | |
| 831 | 0 | index = index + numberOfWrittenBytes + 1; |
| 832 | 0 | buffer[index - 1] = 0; |
| 833 | ||
| 834 | // Write the regular expression string | |
| 835 | 0 | supportsBuffer ? buffer.write(value.source, index, 'utf8') : writeToTypedArray(buffer, value.source, index); |
| 836 | // Adjust the index | |
| 837 | 0 | index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); |
| 838 | // Write zero | |
| 839 | 0 | buffer[index++] = 0x00; |
| 840 | // Write the parameters | |
| 841 | 0 | if(value.global) buffer[index++] = 0x73; // s |
| 842 | 0 | if(value.ignoreCase) buffer[index++] = 0x69; // i |
| 843 | 0 | if(value.multiline) buffer[index++] = 0x6d; // m |
| 844 | // Add ending zero | |
| 845 | 0 | buffer[index++] = 0x00; |
| 846 | 0 | return index; |
| 847 | } else { | |
| 848 | // Write the type | |
| 849 | 0 | buffer[index++] = Array.isArray(value) ? BSON.BSON_DATA_ARRAY : BSON.BSON_DATA_OBJECT; |
| 850 | // Number of written bytes | |
| 851 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 852 | // Adjust the index | |
| 853 | 0 | index = index + numberOfWrittenBytes + 1; |
| 854 | 0 | buffer[index - 1] = 0; |
| 855 | 0 | var endIndex = serializeObject(value, checkKeys, buffer, index + 4, serializeFunctions); |
| 856 | // Write size | |
| 857 | 0 | var size = endIndex - index; |
| 858 | // Write the size of the string to buffer | |
| 859 | 0 | buffer[index++] = size & 0xff; |
| 860 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 861 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 862 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 863 | 0 | return endIndex; |
| 864 | } | |
| 865 | case 'function': | |
| 866 | // WTF for 0.4.X where typeof /someregexp/ === 'function' | |
| 867 | 0 | if(value instanceof RegExp || Object.prototype.toString.call(value) === '[object RegExp]' || String.call(value) == '[object RegExp]') { |
| 868 | // Write the type | |
| 869 | 0 | buffer[index++] = BSON.BSON_DATA_REGEXP; |
| 870 | // Number of written bytes | |
| 871 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 872 | // Encode the name | |
| 873 | 0 | index = index + numberOfWrittenBytes + 1; |
| 874 | 0 | buffer[index - 1] = 0; |
| 875 | ||
| 876 | // Write the regular expression string | |
| 877 | 0 | buffer.write(value.source, index, 'utf8'); |
| 878 | // Adjust the index | |
| 879 | 0 | index = index + (supportsBuffer ? Buffer.byteLength(value.source) : numberOfBytes(value.source)); |
| 880 | // Write zero | |
| 881 | 0 | buffer[index++] = 0x00; |
| 882 | // Write the parameters | |
| 883 | 0 | if(value.global) buffer[index++] = 0x73; // s |
| 884 | 0 | if(value.ignoreCase) buffer[index++] = 0x69; // i |
| 885 | 0 | if(value.multiline) buffer[index++] = 0x6d; // m |
| 886 | // Add ending zero | |
| 887 | 0 | buffer[index++] = 0x00; |
| 888 | 0 | return index; |
| 889 | } else { | |
| 890 | 0 | if(serializeFunctions && value.scope != null && Object.keys(value.scope).length > 0) { |
| 891 | // Write the type | |
| 892 | 0 | buffer[index++] = BSON.BSON_DATA_CODE_W_SCOPE; |
| 893 | // Number of written bytes | |
| 894 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 895 | // Encode the name | |
| 896 | 0 | index = index + numberOfWrittenBytes + 1; |
| 897 | 0 | buffer[index - 1] = 0; |
| 898 | // Calculate the scope size | |
| 899 | 0 | var scopeSize = BSON.calculateObjectSize(value.scope, serializeFunctions); |
| 900 | // Function string | |
| 901 | 0 | var functionString = value.toString(); |
| 902 | // Function Size | |
| 903 | 0 | var codeSize = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 904 | ||
| 905 | // Calculate full size of the object | |
| 906 | 0 | var totalSize = 4 + codeSize + scopeSize; |
| 907 | ||
| 908 | // Write the total size of the object | |
| 909 | 0 | buffer[index++] = totalSize & 0xff; |
| 910 | 0 | buffer[index++] = (totalSize >> 8) & 0xff; |
| 911 | 0 | buffer[index++] = (totalSize >> 16) & 0xff; |
| 912 | 0 | buffer[index++] = (totalSize >> 24) & 0xff; |
| 913 | ||
| 914 | // Write the size of the string to buffer | |
| 915 | 0 | buffer[index++] = codeSize & 0xff; |
| 916 | 0 | buffer[index++] = (codeSize >> 8) & 0xff; |
| 917 | 0 | buffer[index++] = (codeSize >> 16) & 0xff; |
| 918 | 0 | buffer[index++] = (codeSize >> 24) & 0xff; |
| 919 | ||
| 920 | // Write the string | |
| 921 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 922 | // Update index | |
| 923 | 0 | index = index + codeSize - 1; |
| 924 | // Write zero | |
| 925 | 0 | buffer[index++] = 0; |
| 926 | // Serialize the scope object | |
| 927 | 0 | var scopeObjectBuffer = new Buffer(scopeSize); |
| 928 | // Execute the serialization into a seperate buffer | |
| 929 | 0 | serializeObject(value.scope, checkKeys, scopeObjectBuffer, 0, serializeFunctions); |
| 930 | ||
| 931 | // Adjusted scope Size (removing the header) | |
| 932 | 0 | var scopeDocSize = scopeSize - 4; |
| 933 | // Write scope object size | |
| 934 | 0 | buffer[index++] = scopeDocSize & 0xff; |
| 935 | 0 | buffer[index++] = (scopeDocSize >> 8) & 0xff; |
| 936 | 0 | buffer[index++] = (scopeDocSize >> 16) & 0xff; |
| 937 | 0 | buffer[index++] = (scopeDocSize >> 24) & 0xff; |
| 938 | ||
| 939 | // Write the scopeObject into the buffer | |
| 940 | 0 | scopeObjectBuffer.copy(buffer, index, 0, scopeSize); |
| 941 | ||
| 942 | // Adjust index, removing the empty size of the doc (5 bytes 0000000005) | |
| 943 | 0 | index = index + scopeDocSize - 5; |
| 944 | // Write trailing zero | |
| 945 | 0 | buffer[index++] = 0; |
| 946 | 0 | return index |
| 947 | 0 | } else if(serializeFunctions) { |
| 948 | 0 | buffer[index++] = BSON.BSON_DATA_CODE; |
| 949 | // Number of written bytes | |
| 950 | 0 | var numberOfWrittenBytes = supportsBuffer ? buffer.write(name, index, 'utf8') : writeToTypedArray(buffer, name, index); |
| 951 | // Encode the name | |
| 952 | 0 | index = index + numberOfWrittenBytes + 1; |
| 953 | 0 | buffer[index - 1] = 0; |
| 954 | // Function string | |
| 955 | 0 | var functionString = value.toString(); |
| 956 | // Function Size | |
| 957 | 0 | var size = supportsBuffer ? Buffer.byteLength(functionString) + 1 : numberOfBytes(functionString) + 1; |
| 958 | // Write the size of the string to buffer | |
| 959 | 0 | buffer[index++] = size & 0xff; |
| 960 | 0 | buffer[index++] = (size >> 8) & 0xff; |
| 961 | 0 | buffer[index++] = (size >> 16) & 0xff; |
| 962 | 0 | buffer[index++] = (size >> 24) & 0xff; |
| 963 | // Write the string | |
| 964 | 0 | supportsBuffer ? buffer.write(functionString, index, 'utf8') : writeToTypedArray(buffer, functionString, index); |
| 965 | // Update index | |
| 966 | 0 | index = index + size - 1; |
| 967 | // Write zero | |
| 968 | 0 | buffer[index++] = 0; |
| 969 | 0 | return index; |
| 970 | } | |
| 971 | } | |
| 972 | } | |
| 973 | ||
| 974 | // If no value to serialize | |
| 975 | 0 | return index; |
| 976 | } | |
| 977 | ||
| 978 | /** | |
| 979 | * Serialize a Javascript object. | |
| 980 | * | |
| 981 | * @param {Object} object the Javascript object to serialize. | |
| 982 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 983 | * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. | |
| 984 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 985 | * @return {Buffer} returns the Buffer object containing the serialized object. | |
| 986 | * @api public | |
| 987 | */ | |
| 988 | 1 | BSON.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { |
| 989 | // Throw error if we are trying serialize an illegal type | |
| 990 | 0 | if(object == null || typeof object != 'object' || Array.isArray(object)) |
| 991 | 0 | throw new Error("Only javascript objects supported"); |
| 992 | ||
| 993 | // Emoty target buffer | |
| 994 | 0 | var buffer = null; |
| 995 | // Calculate the size of the object | |
| 996 | 0 | var size = BSON.calculateObjectSize(object, serializeFunctions); |
| 997 | // Fetch the best available type for storing the binary data | |
| 998 | 0 | if(buffer = typeof Buffer != 'undefined') { |
| 999 | 0 | buffer = new Buffer(size); |
| 1000 | 0 | asBuffer = true; |
| 1001 | 0 | } else if(typeof Uint8Array != 'undefined') { |
| 1002 | 0 | buffer = new Uint8Array(new ArrayBuffer(size)); |
| 1003 | } else { | |
| 1004 | 0 | buffer = new Array(size); |
| 1005 | } | |
| 1006 | ||
| 1007 | // If asBuffer is false use typed arrays | |
| 1008 | 0 | BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, 0, serializeFunctions); |
| 1009 | 0 | return buffer; |
| 1010 | } | |
| 1011 | ||
| 1012 | /** | |
| 1013 | * Contains the function cache if we have that enable to allow for avoiding the eval step on each deserialization, comparison is by md5 | |
| 1014 | * | |
| 1015 | * @ignore | |
| 1016 | * @api private | |
| 1017 | */ | |
| 1018 | 1 | var functionCache = BSON.functionCache = {}; |
| 1019 | ||
| 1020 | /** | |
| 1021 | * Crc state variables shared by function | |
| 1022 | * | |
| 1023 | * @ignore | |
| 1024 | * @api private | |
| 1025 | */ | |
| 1026 | 1 | var table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D]; |
| 1027 | ||
| 1028 | /** | |
| 1029 | * CRC32 hash method, Fast and enough versitility for our usage | |
| 1030 | * | |
| 1031 | * @ignore | |
| 1032 | * @api private | |
| 1033 | */ | |
| 1034 | 1 | var crc32 = function(string, start, end) { |
| 1035 | 0 | var crc = 0 |
| 1036 | 0 | var x = 0; |
| 1037 | 0 | var y = 0; |
| 1038 | 0 | crc = crc ^ (-1); |
| 1039 | ||
| 1040 | 0 | for(var i = start, iTop = end; i < iTop;i++) { |
| 1041 | 0 | y = (crc ^ string[i]) & 0xFF; |
| 1042 | 0 | x = table[y]; |
| 1043 | 0 | crc = (crc >>> 8) ^ x; |
| 1044 | } | |
| 1045 | ||
| 1046 | 0 | return crc ^ (-1); |
| 1047 | } | |
| 1048 | ||
| 1049 | /** | |
| 1050 | * Deserialize stream data as BSON documents. | |
| 1051 | * | |
| 1052 | * Options | |
| 1053 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1054 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1055 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1056 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 1057 | * | |
| 1058 | * @param {Buffer} data the buffer containing the serialized set of BSON documents. | |
| 1059 | * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. | |
| 1060 | * @param {Number} numberOfDocuments number of documents to deserialize. | |
| 1061 | * @param {Array} documents an array where to store the deserialized documents. | |
| 1062 | * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. | |
| 1063 | * @param {Object} [options] additional options used for the deserialization. | |
| 1064 | * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. | |
| 1065 | * @api public | |
| 1066 | */ | |
| 1067 | 1 | BSON.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { |
| 1068 | // if(numberOfDocuments !== documents.length) throw new Error("Number of expected results back is less than the number of documents"); | |
| 1069 | 0 | options = options != null ? options : {}; |
| 1070 | 0 | var index = startIndex; |
| 1071 | // Loop over all documents | |
| 1072 | 0 | for(var i = 0; i < numberOfDocuments; i++) { |
| 1073 | // Find size of the document | |
| 1074 | 0 | var size = data[index] | data[index + 1] << 8 | data[index + 2] << 16 | data[index + 3] << 24; |
| 1075 | // Update options with index | |
| 1076 | 0 | options['index'] = index; |
| 1077 | // Parse the document at this point | |
| 1078 | 0 | documents[docStartIndex + i] = BSON.deserialize(data, options); |
| 1079 | // Adjust index by the document size | |
| 1080 | 0 | index = index + size; |
| 1081 | } | |
| 1082 | ||
| 1083 | // Return object containing end index of parsing and list of documents | |
| 1084 | 0 | return index; |
| 1085 | } | |
| 1086 | ||
| 1087 | /** | |
| 1088 | * Ensure eval is isolated. | |
| 1089 | * | |
| 1090 | * @ignore | |
| 1091 | * @api private | |
| 1092 | */ | |
| 1093 | 1 | var isolateEvalWithHash = function(functionCache, hash, functionString, object) { |
| 1094 | // Contains the value we are going to set | |
| 1095 | 0 | var value = null; |
| 1096 | ||
| 1097 | // Check for cache hit, eval if missing and return cached function | |
| 1098 | 0 | if(functionCache[hash] == null) { |
| 1099 | 0 | eval("value = " + functionString); |
| 1100 | 0 | functionCache[hash] = value; |
| 1101 | } | |
| 1102 | // Set the object | |
| 1103 | 0 | return functionCache[hash].bind(object); |
| 1104 | } | |
| 1105 | ||
| 1106 | /** | |
| 1107 | * Ensure eval is isolated. | |
| 1108 | * | |
| 1109 | * @ignore | |
| 1110 | * @api private | |
| 1111 | */ | |
| 1112 | 1 | var isolateEval = function(functionString) { |
| 1113 | // Contains the value we are going to set | |
| 1114 | 0 | var value = null; |
| 1115 | // Eval the function | |
| 1116 | 0 | eval("value = " + functionString); |
| 1117 | 0 | return value; |
| 1118 | } | |
| 1119 | ||
| 1120 | /** | |
| 1121 | * Convert Uint8Array to String | |
| 1122 | * | |
| 1123 | * @ignore | |
| 1124 | * @api private | |
| 1125 | */ | |
| 1126 | 1 | var convertUint8ArrayToUtf8String = function(byteArray, startIndex, endIndex) { |
| 1127 | 0 | return BinaryParser.decode_utf8(convertArraytoUtf8BinaryString(byteArray, startIndex, endIndex)); |
| 1128 | } | |
| 1129 | ||
| 1130 | 1 | var convertArraytoUtf8BinaryString = function(byteArray, startIndex, endIndex) { |
| 1131 | 0 | var result = ""; |
| 1132 | 0 | for(var i = startIndex; i < endIndex; i++) { |
| 1133 | 0 | result = result + String.fromCharCode(byteArray[i]); |
| 1134 | } | |
| 1135 | ||
| 1136 | 0 | return result; |
| 1137 | }; | |
| 1138 | ||
| 1139 | /** | |
| 1140 | * Deserialize data as BSON. | |
| 1141 | * | |
| 1142 | * Options | |
| 1143 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1144 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1145 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1146 | * - **promoteLongs** {Boolean, default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits | |
| 1147 | * | |
| 1148 | * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. | |
| 1149 | * @param {Object} [options] additional options used for the deserialization. | |
| 1150 | * @param {Boolean} [isArray] ignore used for recursive parsing. | |
| 1151 | * @return {Object} returns the deserialized Javascript Object. | |
| 1152 | * @api public | |
| 1153 | */ | |
| 1154 | 1 | BSON.deserialize = function(buffer, options, isArray) { |
| 1155 | // Options | |
| 1156 | 0 | options = options == null ? {} : options; |
| 1157 | 0 | var evalFunctions = options['evalFunctions'] == null ? false : options['evalFunctions']; |
| 1158 | 0 | var cacheFunctions = options['cacheFunctions'] == null ? false : options['cacheFunctions']; |
| 1159 | 0 | var cacheFunctionsCrc32 = options['cacheFunctionsCrc32'] == null ? false : options['cacheFunctionsCrc32']; |
| 1160 | 0 | var promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; |
| 1161 | ||
| 1162 | // Validate that we have at least 4 bytes of buffer | |
| 1163 | 0 | if(buffer.length < 5) throw new Error("corrupt bson message < 5 bytes long"); |
| 1164 | ||
| 1165 | // Set up index | |
| 1166 | 0 | var index = typeof options['index'] == 'number' ? options['index'] : 0; |
| 1167 | // Reads in a C style string | |
| 1168 | 0 | var readCStyleString = function() { |
| 1169 | // Get the start search index | |
| 1170 | 0 | var i = index; |
| 1171 | // Locate the end of the c string | |
| 1172 | 0 | while(buffer[i] !== 0x00 && i < buffer.length) { |
| 1173 | 0 | i++ |
| 1174 | } | |
| 1175 | // If are at the end of the buffer there is a problem with the document | |
| 1176 | 0 | if(i >= buffer.length) throw new Error("Bad BSON Document: illegal CString") |
| 1177 | // Grab utf8 encoded string | |
| 1178 | 0 | var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, i) : convertUint8ArrayToUtf8String(buffer, index, i); |
| 1179 | // Update index position | |
| 1180 | 0 | index = i + 1; |
| 1181 | // Return string | |
| 1182 | 0 | return string; |
| 1183 | } | |
| 1184 | ||
| 1185 | // Create holding object | |
| 1186 | 0 | var object = isArray ? [] : {}; |
| 1187 | ||
| 1188 | // Read the document size | |
| 1189 | 0 | var size = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1190 | ||
| 1191 | // Ensure buffer is valid size | |
| 1192 | 0 | if(size < 5 || size > buffer.length) throw new Error("corrupt bson message"); |
| 1193 | ||
| 1194 | // While we have more left data left keep parsing | |
| 1195 | 0 | while(true) { |
| 1196 | // Read the type | |
| 1197 | 0 | var elementType = buffer[index++]; |
| 1198 | // If we get a zero it's the last byte, exit | |
| 1199 | 0 | if(elementType == 0) break; |
| 1200 | // Read the name of the field | |
| 1201 | 0 | var name = readCStyleString(); |
| 1202 | // Switch on the type | |
| 1203 | 0 | switch(elementType) { |
| 1204 | case BSON.BSON_DATA_OID: | |
| 1205 | 0 | var string = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('binary', index, index + 12) : convertArraytoUtf8BinaryString(buffer, index, index + 12); |
| 1206 | // Decode the oid | |
| 1207 | 0 | object[name] = new ObjectID(string); |
| 1208 | // Update index | |
| 1209 | 0 | index = index + 12; |
| 1210 | 0 | break; |
| 1211 | case BSON.BSON_DATA_STRING: | |
| 1212 | // Read the content of the field | |
| 1213 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1214 | // Add string to object | |
| 1215 | 0 | object[name] = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1216 | // Update parse index position | |
| 1217 | 0 | index = index + stringSize; |
| 1218 | 0 | break; |
| 1219 | case BSON.BSON_DATA_INT: | |
| 1220 | // Decode the 32bit value | |
| 1221 | 0 | object[name] = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1222 | 0 | break; |
| 1223 | case BSON.BSON_DATA_NUMBER: | |
| 1224 | // Decode the double value | |
| 1225 | 0 | object[name] = readIEEE754(buffer, index, 'little', 52, 8); |
| 1226 | // Update the index | |
| 1227 | 0 | index = index + 8; |
| 1228 | 0 | break; |
| 1229 | case BSON.BSON_DATA_DATE: | |
| 1230 | // Unpack the low and high bits | |
| 1231 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1232 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1233 | // Set date object | |
| 1234 | 0 | object[name] = new Date(new Long(lowBits, highBits).toNumber()); |
| 1235 | 0 | break; |
| 1236 | case BSON.BSON_DATA_BOOLEAN: | |
| 1237 | // Parse the boolean value | |
| 1238 | 0 | object[name] = buffer[index++] == 1; |
| 1239 | 0 | break; |
| 1240 | case BSON.BSON_DATA_NULL: | |
| 1241 | // Parse the boolean value | |
| 1242 | 0 | object[name] = null; |
| 1243 | 0 | break; |
| 1244 | case BSON.BSON_DATA_BINARY: | |
| 1245 | // Decode the size of the binary blob | |
| 1246 | 0 | var binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1247 | // Decode the subtype | |
| 1248 | 0 | var subType = buffer[index++]; |
| 1249 | // Decode as raw Buffer object if options specifies it | |
| 1250 | 0 | if(buffer['slice'] != null) { |
| 1251 | // If we have subtype 2 skip the 4 bytes for the size | |
| 1252 | 0 | if(subType == Binary.SUBTYPE_BYTE_ARRAY) { |
| 1253 | 0 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1254 | } | |
| 1255 | // Slice the data | |
| 1256 | 0 | object[name] = new Binary(buffer.slice(index, index + binarySize), subType); |
| 1257 | } else { | |
| 1258 | 0 | var _buffer = typeof Uint8Array != 'undefined' ? new Uint8Array(new ArrayBuffer(binarySize)) : new Array(binarySize); |
| 1259 | // If we have subtype 2 skip the 4 bytes for the size | |
| 1260 | 0 | if(subType == Binary.SUBTYPE_BYTE_ARRAY) { |
| 1261 | 0 | binarySize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1262 | } | |
| 1263 | // Copy the data | |
| 1264 | 0 | for(var i = 0; i < binarySize; i++) { |
| 1265 | 0 | _buffer[i] = buffer[index + i]; |
| 1266 | } | |
| 1267 | // Create the binary object | |
| 1268 | 0 | object[name] = new Binary(_buffer, subType); |
| 1269 | } | |
| 1270 | // Update the index | |
| 1271 | 0 | index = index + binarySize; |
| 1272 | 0 | break; |
| 1273 | case BSON.BSON_DATA_ARRAY: | |
| 1274 | 0 | options['index'] = index; |
| 1275 | // Decode the size of the array document | |
| 1276 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1277 | // Set the array to the object | |
| 1278 | 0 | object[name] = BSON.deserialize(buffer, options, true); |
| 1279 | // Adjust the index | |
| 1280 | 0 | index = index + objectSize; |
| 1281 | 0 | break; |
| 1282 | case BSON.BSON_DATA_OBJECT: | |
| 1283 | 0 | options['index'] = index; |
| 1284 | // Decode the size of the object document | |
| 1285 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1286 | // Set the array to the object | |
| 1287 | 0 | object[name] = BSON.deserialize(buffer, options, false); |
| 1288 | // Adjust the index | |
| 1289 | 0 | index = index + objectSize; |
| 1290 | 0 | break; |
| 1291 | case BSON.BSON_DATA_REGEXP: | |
| 1292 | // Create the regexp | |
| 1293 | 0 | var source = readCStyleString(); |
| 1294 | 0 | var regExpOptions = readCStyleString(); |
| 1295 | // For each option add the corresponding one for javascript | |
| 1296 | 0 | var optionsArray = new Array(regExpOptions.length); |
| 1297 | ||
| 1298 | // Parse options | |
| 1299 | 0 | for(var i = 0; i < regExpOptions.length; i++) { |
| 1300 | 0 | switch(regExpOptions[i]) { |
| 1301 | case 'm': | |
| 1302 | 0 | optionsArray[i] = 'm'; |
| 1303 | 0 | break; |
| 1304 | case 's': | |
| 1305 | 0 | optionsArray[i] = 'g'; |
| 1306 | 0 | break; |
| 1307 | case 'i': | |
| 1308 | 0 | optionsArray[i] = 'i'; |
| 1309 | 0 | break; |
| 1310 | } | |
| 1311 | } | |
| 1312 | ||
| 1313 | 0 | object[name] = new RegExp(source, optionsArray.join('')); |
| 1314 | 0 | break; |
| 1315 | case BSON.BSON_DATA_LONG: | |
| 1316 | // Unpack the low and high bits | |
| 1317 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1318 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1319 | // Create long object | |
| 1320 | 0 | var long = new Long(lowBits, highBits); |
| 1321 | // Promote the long if possible | |
| 1322 | 0 | if(promoteLongs) { |
| 1323 | 0 | object[name] = long.lessThanOrEqual(JS_INT_MAX_LONG) && long.greaterThanOrEqual(JS_INT_MIN_LONG) ? long.toNumber() : long; |
| 1324 | } else { | |
| 1325 | 0 | object[name] = long; |
| 1326 | } | |
| 1327 | 0 | break; |
| 1328 | case BSON.BSON_DATA_SYMBOL: | |
| 1329 | // Read the content of the field | |
| 1330 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1331 | // Add string to object | |
| 1332 | 0 | object[name] = new Symbol(buffer.toString('utf8', index, index + stringSize - 1)); |
| 1333 | // Update parse index position | |
| 1334 | 0 | index = index + stringSize; |
| 1335 | 0 | break; |
| 1336 | case BSON.BSON_DATA_TIMESTAMP: | |
| 1337 | // Unpack the low and high bits | |
| 1338 | 0 | var lowBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1339 | 0 | var highBits = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1340 | // Set the object | |
| 1341 | 0 | object[name] = new Timestamp(lowBits, highBits); |
| 1342 | 0 | break; |
| 1343 | case BSON.BSON_DATA_MIN_KEY: | |
| 1344 | // Parse the object | |
| 1345 | 0 | object[name] = new MinKey(); |
| 1346 | 0 | break; |
| 1347 | case BSON.BSON_DATA_MAX_KEY: | |
| 1348 | // Parse the object | |
| 1349 | 0 | object[name] = new MaxKey(); |
| 1350 | 0 | break; |
| 1351 | case BSON.BSON_DATA_CODE: | |
| 1352 | // Read the content of the field | |
| 1353 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1354 | // Function string | |
| 1355 | 0 | var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1356 | ||
| 1357 | // If we are evaluating the functions | |
| 1358 | 0 | if(evalFunctions) { |
| 1359 | // Contains the value we are going to set | |
| 1360 | 0 | var value = null; |
| 1361 | // If we have cache enabled let's look for the md5 of the function in the cache | |
| 1362 | 0 | if(cacheFunctions) { |
| 1363 | 0 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
| 1364 | // Got to do this to avoid V8 deoptimizing the call due to finding eval | |
| 1365 | 0 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
| 1366 | } else { | |
| 1367 | // Set directly | |
| 1368 | 0 | object[name] = isolateEval(functionString); |
| 1369 | } | |
| 1370 | } else { | |
| 1371 | 0 | object[name] = new Code(functionString, {}); |
| 1372 | } | |
| 1373 | ||
| 1374 | // Update parse index position | |
| 1375 | 0 | index = index + stringSize; |
| 1376 | 0 | break; |
| 1377 | case BSON.BSON_DATA_CODE_W_SCOPE: | |
| 1378 | // Read the content of the field | |
| 1379 | 0 | var totalSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1380 | 0 | var stringSize = buffer[index++] | buffer[index++] << 8 | buffer[index++] << 16 | buffer[index++] << 24; |
| 1381 | // Javascript function | |
| 1382 | 0 | var functionString = supportsBuffer && Buffer.isBuffer(buffer) ? buffer.toString('utf8', index, index + stringSize - 1) : convertUint8ArrayToUtf8String(buffer, index, index + stringSize - 1); |
| 1383 | // Update parse index position | |
| 1384 | 0 | index = index + stringSize; |
| 1385 | // Parse the element | |
| 1386 | 0 | options['index'] = index; |
| 1387 | // Decode the size of the object document | |
| 1388 | 0 | var objectSize = buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; |
| 1389 | // Decode the scope object | |
| 1390 | 0 | var scopeObject = BSON.deserialize(buffer, options, false); |
| 1391 | // Adjust the index | |
| 1392 | 0 | index = index + objectSize; |
| 1393 | ||
| 1394 | // If we are evaluating the functions | |
| 1395 | 0 | if(evalFunctions) { |
| 1396 | // Contains the value we are going to set | |
| 1397 | 0 | var value = null; |
| 1398 | // If we have cache enabled let's look for the md5 of the function in the cache | |
| 1399 | 0 | if(cacheFunctions) { |
| 1400 | 0 | var hash = cacheFunctionsCrc32 ? crc32(functionString) : functionString; |
| 1401 | // Got to do this to avoid V8 deoptimizing the call due to finding eval | |
| 1402 | 0 | object[name] = isolateEvalWithHash(functionCache, hash, functionString, object); |
| 1403 | } else { | |
| 1404 | // Set directly | |
| 1405 | 0 | object[name] = isolateEval(functionString); |
| 1406 | } | |
| 1407 | ||
| 1408 | // Set the scope on the object | |
| 1409 | 0 | object[name].scope = scopeObject; |
| 1410 | } else { | |
| 1411 | 0 | object[name] = new Code(functionString, scopeObject); |
| 1412 | } | |
| 1413 | ||
| 1414 | // Add string to object | |
| 1415 | 0 | break; |
| 1416 | } | |
| 1417 | } | |
| 1418 | ||
| 1419 | // Check if we have a db ref object | |
| 1420 | 0 | if(object['$id'] != null) object = new DBRef(object['$ref'], object['$id'], object['$db']); |
| 1421 | ||
| 1422 | // Return the final objects | |
| 1423 | 0 | return object; |
| 1424 | } | |
| 1425 | ||
| 1426 | /** | |
| 1427 | * Check if key name is valid. | |
| 1428 | * | |
| 1429 | * @ignore | |
| 1430 | * @api private | |
| 1431 | */ | |
| 1432 | 1 | BSON.checkKey = function checkKey (key, dollarsAndDotsOk) { |
| 1433 | 0 | if (!key.length) return; |
| 1434 | // Check if we have a legal key for the object | |
| 1435 | 0 | if (!!~key.indexOf("\x00")) { |
| 1436 | // The BSON spec doesn't allow keys with null bytes because keys are | |
| 1437 | // null-terminated. | |
| 1438 | 0 | throw Error("key " + key + " must not contain null bytes"); |
| 1439 | } | |
| 1440 | 0 | if (!dollarsAndDotsOk) { |
| 1441 | 0 | if('$' == key[0]) { |
| 1442 | 0 | throw Error("key " + key + " must not start with '$'"); |
| 1443 | 0 | } else if (!!~key.indexOf('.')) { |
| 1444 | 0 | throw Error("key " + key + " must not contain '.'"); |
| 1445 | } | |
| 1446 | } | |
| 1447 | }; | |
| 1448 | ||
| 1449 | /** | |
| 1450 | * Deserialize data as BSON. | |
| 1451 | * | |
| 1452 | * Options | |
| 1453 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1454 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1455 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1456 | * | |
| 1457 | * @param {Buffer} buffer the buffer containing the serialized set of BSON documents. | |
| 1458 | * @param {Object} [options] additional options used for the deserialization. | |
| 1459 | * @param {Boolean} [isArray] ignore used for recursive parsing. | |
| 1460 | * @return {Object} returns the deserialized Javascript Object. | |
| 1461 | * @api public | |
| 1462 | */ | |
| 1463 | 1 | BSON.prototype.deserialize = function(data, options) { |
| 1464 | 0 | return BSON.deserialize(data, options); |
| 1465 | } | |
| 1466 | ||
| 1467 | /** | |
| 1468 | * Deserialize stream data as BSON documents. | |
| 1469 | * | |
| 1470 | * Options | |
| 1471 | * - **evalFunctions** {Boolean, default:false}, evaluate functions in the BSON document scoped to the object deserialized. | |
| 1472 | * - **cacheFunctions** {Boolean, default:false}, cache evaluated functions for reuse. | |
| 1473 | * - **cacheFunctionsCrc32** {Boolean, default:false}, use a crc32 code for caching, otherwise use the string of the function. | |
| 1474 | * | |
| 1475 | * @param {Buffer} data the buffer containing the serialized set of BSON documents. | |
| 1476 | * @param {Number} startIndex the start index in the data Buffer where the deserialization is to start. | |
| 1477 | * @param {Number} numberOfDocuments number of documents to deserialize. | |
| 1478 | * @param {Array} documents an array where to store the deserialized documents. | |
| 1479 | * @param {Number} docStartIndex the index in the documents array from where to start inserting documents. | |
| 1480 | * @param {Object} [options] additional options used for the deserialization. | |
| 1481 | * @return {Number} returns the next index in the buffer after deserialization **x** numbers of documents. | |
| 1482 | * @api public | |
| 1483 | */ | |
| 1484 | 1 | BSON.prototype.deserializeStream = function(data, startIndex, numberOfDocuments, documents, docStartIndex, options) { |
| 1485 | 0 | return BSON.deserializeStream(data, startIndex, numberOfDocuments, documents, docStartIndex, options); |
| 1486 | } | |
| 1487 | ||
| 1488 | /** | |
| 1489 | * Serialize a Javascript object. | |
| 1490 | * | |
| 1491 | * @param {Object} object the Javascript object to serialize. | |
| 1492 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 1493 | * @param {Boolean} asBuffer return the serialized object as a Buffer object **(ignore)**. | |
| 1494 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 1495 | * @return {Buffer} returns the Buffer object containing the serialized object. | |
| 1496 | * @api public | |
| 1497 | */ | |
| 1498 | 1 | BSON.prototype.serialize = function(object, checkKeys, asBuffer, serializeFunctions) { |
| 1499 | 0 | return BSON.serialize(object, checkKeys, asBuffer, serializeFunctions); |
| 1500 | } | |
| 1501 | ||
| 1502 | /** | |
| 1503 | * Calculate the bson size for a passed in Javascript object. | |
| 1504 | * | |
| 1505 | * @param {Object} object the Javascript object to calculate the BSON byte size for. | |
| 1506 | * @param {Boolean} [serializeFunctions] serialize all functions in the object **(default:false)**. | |
| 1507 | * @return {Number} returns the number of bytes the BSON object will take up. | |
| 1508 | * @api public | |
| 1509 | */ | |
| 1510 | 1 | BSON.prototype.calculateObjectSize = function(object, serializeFunctions) { |
| 1511 | 0 | return BSON.calculateObjectSize(object, serializeFunctions); |
| 1512 | } | |
| 1513 | ||
| 1514 | /** | |
| 1515 | * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization. | |
| 1516 | * | |
| 1517 | * @param {Object} object the Javascript object to serialize. | |
| 1518 | * @param {Boolean} checkKeys the serializer will check if keys are valid. | |
| 1519 | * @param {Buffer} buffer the Buffer you pre-allocated to store the serialized BSON object. | |
| 1520 | * @param {Number} index the index in the buffer where we wish to start serializing into. | |
| 1521 | * @param {Boolean} serializeFunctions serialize the javascript functions **(default:false)**. | |
| 1522 | * @return {Number} returns the new write index in the Buffer. | |
| 1523 | * @api public | |
| 1524 | */ | |
| 1525 | 1 | BSON.prototype.serializeWithBufferAndIndex = function(object, checkKeys, buffer, startIndex, serializeFunctions) { |
| 1526 | 0 | return BSON.serializeWithBufferAndIndex(object, checkKeys, buffer, startIndex, serializeFunctions); |
| 1527 | } | |
| 1528 | ||
| 1529 | /** | |
| 1530 | * @ignore | |
| 1531 | * @api private | |
| 1532 | */ | |
| 1533 | 1 | exports.Code = Code; |
| 1534 | 1 | exports.Symbol = Symbol; |
| 1535 | 1 | exports.BSON = BSON; |
| 1536 | 1 | exports.DBRef = DBRef; |
| 1537 | 1 | exports.Binary = Binary; |
| 1538 | 1 | exports.ObjectID = ObjectID; |
| 1539 | 1 | exports.Long = Long; |
| 1540 | 1 | exports.Timestamp = Timestamp; |
| 1541 | 1 | exports.Double = Double; |
| 1542 | 1 | exports.MinKey = MinKey; |
| 1543 | 1 | exports.MaxKey = MaxKey; |
| 1544 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Code type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Code type. | |
| 5 | * @param {String|Function} code a string or function. | |
| 6 | * @param {Object} [scope] an optional scope for the function. | |
| 7 | * @return {Code} | |
| 8 | */ | |
| 9 | 1 | function Code(code, scope) { |
| 10 | 0 | if(!(this instanceof Code)) return new Code(code, scope); |
| 11 | ||
| 12 | 0 | this._bsontype = 'Code'; |
| 13 | 0 | this.code = code; |
| 14 | 0 | this.scope = scope == null ? {} : scope; |
| 15 | }; | |
| 16 | ||
| 17 | /** | |
| 18 | * @ignore | |
| 19 | * @api private | |
| 20 | */ | |
| 21 | 1 | Code.prototype.toJSON = function() { |
| 22 | 0 | return {scope:this.scope, code:this.code}; |
| 23 | } | |
| 24 | ||
| 25 | 1 | exports.Code = Code; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON DBRef type. | |
| 3 | * | |
| 4 | * @class Represents the BSON DBRef type. | |
| 5 | * @param {String} namespace the collection name. | |
| 6 | * @param {ObjectID} oid the reference ObjectID. | |
| 7 | * @param {String} [db] optional db name, if omitted the reference is local to the current db. | |
| 8 | * @return {DBRef} | |
| 9 | */ | |
| 10 | 1 | function DBRef(namespace, oid, db) { |
| 11 | 0 | if(!(this instanceof DBRef)) return new DBRef(namespace, oid, db); |
| 12 | ||
| 13 | 0 | this._bsontype = 'DBRef'; |
| 14 | 0 | this.namespace = namespace; |
| 15 | 0 | this.oid = oid; |
| 16 | 0 | this.db = db; |
| 17 | }; | |
| 18 | ||
| 19 | /** | |
| 20 | * @ignore | |
| 21 | * @api private | |
| 22 | */ | |
| 23 | 1 | DBRef.prototype.toJSON = function() { |
| 24 | 0 | return { |
| 25 | '$ref':this.namespace, | |
| 26 | '$id':this.oid, | |
| 27 | '$db':this.db == null ? '' : this.db | |
| 28 | }; | |
| 29 | } | |
| 30 | ||
| 31 | 1 | exports.DBRef = DBRef; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Double type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Double type. | |
| 5 | * @param {Number} value the number we want to represent as a double. | |
| 6 | * @return {Double} | |
| 7 | */ | |
| 8 | 1 | function Double(value) { |
| 9 | 0 | if(!(this instanceof Double)) return new Double(value); |
| 10 | ||
| 11 | 0 | this._bsontype = 'Double'; |
| 12 | 0 | this.value = value; |
| 13 | } | |
| 14 | ||
| 15 | /** | |
| 16 | * Access the number value. | |
| 17 | * | |
| 18 | * @return {Number} returns the wrapped double number. | |
| 19 | * @api public | |
| 20 | */ | |
| 21 | 1 | Double.prototype.valueOf = function() { |
| 22 | 0 | return this.value; |
| 23 | }; | |
| 24 | ||
| 25 | /** | |
| 26 | * @ignore | |
| 27 | * @api private | |
| 28 | */ | |
| 29 | 1 | Double.prototype.toJSON = function() { |
| 30 | 0 | return this.value; |
| 31 | } | |
| 32 | ||
| 33 | 1 | exports.Double = Double; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Copyright (c) 2008, Fair Oaks Labs, Inc. | |
| 2 | // All rights reserved. | |
| 3 | // | |
| 4 | // Redistribution and use in source and binary forms, with or without | |
| 5 | // modification, are permitted provided that the following conditions are met: | |
| 6 | // | |
| 7 | // * Redistributions of source code must retain the above copyright notice, | |
| 8 | // this list of conditions and the following disclaimer. | |
| 9 | // | |
| 10 | // * Redistributions in binary form must reproduce the above copyright notice, | |
| 11 | // this list of conditions and the following disclaimer in the documentation | |
| 12 | // and/or other materials provided with the distribution. | |
| 13 | // | |
| 14 | // * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors | |
| 15 | // may be used to endorse or promote products derived from this software | |
| 16 | // without specific prior written permission. | |
| 17 | // | |
| 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 19 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 21 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
| 22 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| 23 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| 24 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| 25 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| 26 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| 27 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| 28 | // POSSIBILITY OF SUCH DAMAGE. | |
| 29 | // | |
| 30 | // | |
| 31 | // Modifications to writeIEEE754 to support negative zeroes made by Brian White | |
| 32 | ||
| 33 | 1 | var readIEEE754 = function(buffer, offset, endian, mLen, nBytes) { |
| 34 | 0 | var e, m, |
| 35 | bBE = (endian === 'big'), | |
| 36 | eLen = nBytes * 8 - mLen - 1, | |
| 37 | eMax = (1 << eLen) - 1, | |
| 38 | eBias = eMax >> 1, | |
| 39 | nBits = -7, | |
| 40 | i = bBE ? 0 : (nBytes - 1), | |
| 41 | d = bBE ? 1 : -1, | |
| 42 | s = buffer[offset + i]; | |
| 43 | ||
| 44 | 0 | i += d; |
| 45 | ||
| 46 | 0 | e = s & ((1 << (-nBits)) - 1); |
| 47 | 0 | s >>= (-nBits); |
| 48 | 0 | nBits += eLen; |
| 49 | 0 | for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8); |
| 50 | ||
| 51 | 0 | m = e & ((1 << (-nBits)) - 1); |
| 52 | 0 | e >>= (-nBits); |
| 53 | 0 | nBits += mLen; |
| 54 | 0 | for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8); |
| 55 | ||
| 56 | 0 | if (e === 0) { |
| 57 | 0 | e = 1 - eBias; |
| 58 | 0 | } else if (e === eMax) { |
| 59 | 0 | return m ? NaN : ((s ? -1 : 1) * Infinity); |
| 60 | } else { | |
| 61 | 0 | m = m + Math.pow(2, mLen); |
| 62 | 0 | e = e - eBias; |
| 63 | } | |
| 64 | 0 | return (s ? -1 : 1) * m * Math.pow(2, e - mLen); |
| 65 | }; | |
| 66 | ||
| 67 | 1 | var writeIEEE754 = function(buffer, value, offset, endian, mLen, nBytes) { |
| 68 | 0 | var e, m, c, |
| 69 | bBE = (endian === 'big'), | |
| 70 | eLen = nBytes * 8 - mLen - 1, | |
| 71 | eMax = (1 << eLen) - 1, | |
| 72 | eBias = eMax >> 1, | |
| 73 | rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0), | |
| 74 | i = bBE ? (nBytes-1) : 0, | |
| 75 | d = bBE ? -1 : 1, | |
| 76 | s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; | |
| 77 | ||
| 78 | 0 | value = Math.abs(value); |
| 79 | ||
| 80 | 0 | if (isNaN(value) || value === Infinity) { |
| 81 | 0 | m = isNaN(value) ? 1 : 0; |
| 82 | 0 | e = eMax; |
| 83 | } else { | |
| 84 | 0 | e = Math.floor(Math.log(value) / Math.LN2); |
| 85 | 0 | if (value * (c = Math.pow(2, -e)) < 1) { |
| 86 | 0 | e--; |
| 87 | 0 | c *= 2; |
| 88 | } | |
| 89 | 0 | if (e+eBias >= 1) { |
| 90 | 0 | value += rt / c; |
| 91 | } else { | |
| 92 | 0 | value += rt * Math.pow(2, 1 - eBias); |
| 93 | } | |
| 94 | 0 | if (value * c >= 2) { |
| 95 | 0 | e++; |
| 96 | 0 | c /= 2; |
| 97 | } | |
| 98 | ||
| 99 | 0 | if (e + eBias >= eMax) { |
| 100 | 0 | m = 0; |
| 101 | 0 | e = eMax; |
| 102 | 0 | } else if (e + eBias >= 1) { |
| 103 | 0 | m = (value * c - 1) * Math.pow(2, mLen); |
| 104 | 0 | e = e + eBias; |
| 105 | } else { | |
| 106 | 0 | m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); |
| 107 | 0 | e = 0; |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | 0 | for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8); |
| 112 | ||
| 113 | 0 | e = (e << mLen) | m; |
| 114 | 0 | eLen += mLen; |
| 115 | 0 | for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8); |
| 116 | ||
| 117 | 0 | buffer[offset + i - d] |= s * 128; |
| 118 | }; | |
| 119 | ||
| 120 | 1 | exports.readIEEE754 = readIEEE754; |
| 121 | 1 | exports.writeIEEE754 = writeIEEE754; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | try { |
| 2 | 1 | exports.BSONPure = require('./bson'); |
| 3 | 1 | exports.BSONNative = require('../../ext'); |
| 4 | } catch(err) { | |
| 5 | // do nothing | |
| 6 | } | |
| 7 | ||
| 8 | 1 | [ './binary_parser' |
| 9 | , './binary' | |
| 10 | , './code' | |
| 11 | , './db_ref' | |
| 12 | , './double' | |
| 13 | , './max_key' | |
| 14 | , './min_key' | |
| 15 | , './objectid' | |
| 16 | , './symbol' | |
| 17 | , './timestamp' | |
| 18 | , './long'].forEach(function (path) { | |
| 19 | 11 | var module = require('./' + path); |
| 20 | 11 | for (var i in module) { |
| 21 | 12 | exports[i] = module[i]; |
| 22 | } | |
| 23 | }); | |
| 24 | ||
| 25 | // Exports all the classes for the NATIVE JS BSON Parser | |
| 26 | 1 | exports.native = function() { |
| 27 | 0 | var classes = {}; |
| 28 | // Map all the classes | |
| 29 | 0 | [ './binary_parser' |
| 30 | , './binary' | |
| 31 | , './code' | |
| 32 | , './db_ref' | |
| 33 | , './double' | |
| 34 | , './max_key' | |
| 35 | , './min_key' | |
| 36 | , './objectid' | |
| 37 | , './symbol' | |
| 38 | , './timestamp' | |
| 39 | , './long' | |
| 40 | , '../../ext' | |
| 41 | ].forEach(function (path) { | |
| 42 | 0 | var module = require('./' + path); |
| 43 | 0 | for (var i in module) { |
| 44 | 0 | classes[i] = module[i]; |
| 45 | } | |
| 46 | }); | |
| 47 | // Return classes list | |
| 48 | 0 | return classes; |
| 49 | } | |
| 50 | ||
| 51 | // Exports all the classes for the PURE JS BSON Parser | |
| 52 | 1 | exports.pure = function() { |
| 53 | 0 | var classes = {}; |
| 54 | // Map all the classes | |
| 55 | 0 | [ './binary_parser' |
| 56 | , './binary' | |
| 57 | , './code' | |
| 58 | , './db_ref' | |
| 59 | , './double' | |
| 60 | , './max_key' | |
| 61 | , './min_key' | |
| 62 | , './objectid' | |
| 63 | , './symbol' | |
| 64 | , './timestamp' | |
| 65 | , './long' | |
| 66 | , '././bson'].forEach(function (path) { | |
| 67 | 0 | var module = require('./' + path); |
| 68 | 0 | for (var i in module) { |
| 69 | 0 | classes[i] = module[i]; |
| 70 | } | |
| 71 | }); | |
| 72 | // Return classes list | |
| 73 | 0 | return classes; |
| 74 | } | |
| 75 |
| Line | Hits | Source |
|---|---|---|
| 1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 2 | // you may not use this file except in compliance with the License. | |
| 3 | // You may obtain a copy of the License at | |
| 4 | // | |
| 5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
| 6 | // | |
| 7 | // Unless required by applicable law or agreed to in writing, software | |
| 8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
| 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 10 | // See the License for the specific language governing permissions and | |
| 11 | // limitations under the License. | |
| 12 | // | |
| 13 | // Copyright 2009 Google Inc. All Rights Reserved | |
| 14 | ||
| 15 | /** | |
| 16 | * Defines a Long class for representing a 64-bit two's-complement | |
| 17 | * integer value, which faithfully simulates the behavior of a Java "Long". This | |
| 18 | * implementation is derived from LongLib in GWT. | |
| 19 | * | |
| 20 | * Constructs a 64-bit two's-complement integer, given its low and high 32-bit | |
| 21 | * values as *signed* integers. See the from* functions below for more | |
| 22 | * convenient ways of constructing Longs. | |
| 23 | * | |
| 24 | * The internal representation of a Long is the two given signed, 32-bit values. | |
| 25 | * We use 32-bit pieces because these are the size of integers on which | |
| 26 | * Javascript performs bit-operations. For operations like addition and | |
| 27 | * multiplication, we split each number into 16-bit pieces, which can easily be | |
| 28 | * multiplied within Javascript's floating-point representation without overflow | |
| 29 | * or change in sign. | |
| 30 | * | |
| 31 | * In the algorithms below, we frequently reduce the negative case to the | |
| 32 | * positive case by negating the input(s) and then post-processing the result. | |
| 33 | * Note that we must ALWAYS check specially whether those values are MIN_VALUE | |
| 34 | * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as | |
| 35 | * a positive number, it overflows back into a negative). Not handling this | |
| 36 | * case would often result in infinite recursion. | |
| 37 | * | |
| 38 | * @class Represents the BSON Long type. | |
| 39 | * @param {Number} low the low (signed) 32 bits of the Long. | |
| 40 | * @param {Number} high the high (signed) 32 bits of the Long. | |
| 41 | */ | |
| 42 | 1 | function Long(low, high) { |
| 43 | 10 | if(!(this instanceof Long)) return new Long(low, high); |
| 44 | ||
| 45 | 10 | this._bsontype = 'Long'; |
| 46 | /** | |
| 47 | * @type {number} | |
| 48 | * @api private | |
| 49 | */ | |
| 50 | 10 | this.low_ = low | 0; // force into 32 signed bits. |
| 51 | ||
| 52 | /** | |
| 53 | * @type {number} | |
| 54 | * @api private | |
| 55 | */ | |
| 56 | 10 | this.high_ = high | 0; // force into 32 signed bits. |
| 57 | }; | |
| 58 | ||
| 59 | /** | |
| 60 | * Return the int value. | |
| 61 | * | |
| 62 | * @return {Number} the value, assuming it is a 32-bit integer. | |
| 63 | * @api public | |
| 64 | */ | |
| 65 | 1 | Long.prototype.toInt = function() { |
| 66 | 0 | return this.low_; |
| 67 | }; | |
| 68 | ||
| 69 | /** | |
| 70 | * Return the Number value. | |
| 71 | * | |
| 72 | * @return {Number} the closest floating-point representation to this value. | |
| 73 | * @api public | |
| 74 | */ | |
| 75 | 1 | Long.prototype.toNumber = function() { |
| 76 | 0 | return this.high_ * Long.TWO_PWR_32_DBL_ + |
| 77 | this.getLowBitsUnsigned(); | |
| 78 | }; | |
| 79 | ||
| 80 | /** | |
| 81 | * Return the JSON value. | |
| 82 | * | |
| 83 | * @return {String} the JSON representation. | |
| 84 | * @api public | |
| 85 | */ | |
| 86 | 1 | Long.prototype.toJSON = function() { |
| 87 | 0 | return this.toString(); |
| 88 | } | |
| 89 | ||
| 90 | /** | |
| 91 | * Return the String value. | |
| 92 | * | |
| 93 | * @param {Number} [opt_radix] the radix in which the text should be written. | |
| 94 | * @return {String} the textual representation of this value. | |
| 95 | * @api public | |
| 96 | */ | |
| 97 | 1 | Long.prototype.toString = function(opt_radix) { |
| 98 | 0 | var radix = opt_radix || 10; |
| 99 | 0 | if (radix < 2 || 36 < radix) { |
| 100 | 0 | throw Error('radix out of range: ' + radix); |
| 101 | } | |
| 102 | ||
| 103 | 0 | if (this.isZero()) { |
| 104 | 0 | return '0'; |
| 105 | } | |
| 106 | ||
| 107 | 0 | if (this.isNegative()) { |
| 108 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 109 | // We need to change the Long value before it can be negated, so we remove | |
| 110 | // the bottom-most digit in this base and then recurse to do the rest. | |
| 111 | 0 | var radixLong = Long.fromNumber(radix); |
| 112 | 0 | var div = this.div(radixLong); |
| 113 | 0 | var rem = div.multiply(radixLong).subtract(this); |
| 114 | 0 | return div.toString(radix) + rem.toInt().toString(radix); |
| 115 | } else { | |
| 116 | 0 | return '-' + this.negate().toString(radix); |
| 117 | } | |
| 118 | } | |
| 119 | ||
| 120 | // Do several (6) digits each time through the loop, so as to | |
| 121 | // minimize the calls to the very expensive emulated div. | |
| 122 | 0 | var radixToPower = Long.fromNumber(Math.pow(radix, 6)); |
| 123 | ||
| 124 | 0 | var rem = this; |
| 125 | 0 | var result = ''; |
| 126 | 0 | while (true) { |
| 127 | 0 | var remDiv = rem.div(radixToPower); |
| 128 | 0 | var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); |
| 129 | 0 | var digits = intval.toString(radix); |
| 130 | ||
| 131 | 0 | rem = remDiv; |
| 132 | 0 | if (rem.isZero()) { |
| 133 | 0 | return digits + result; |
| 134 | } else { | |
| 135 | 0 | while (digits.length < 6) { |
| 136 | 0 | digits = '0' + digits; |
| 137 | } | |
| 138 | 0 | result = '' + digits + result; |
| 139 | } | |
| 140 | } | |
| 141 | }; | |
| 142 | ||
| 143 | /** | |
| 144 | * Return the high 32-bits value. | |
| 145 | * | |
| 146 | * @return {Number} the high 32-bits as a signed value. | |
| 147 | * @api public | |
| 148 | */ | |
| 149 | 1 | Long.prototype.getHighBits = function() { |
| 150 | 0 | return this.high_; |
| 151 | }; | |
| 152 | ||
| 153 | /** | |
| 154 | * Return the low 32-bits value. | |
| 155 | * | |
| 156 | * @return {Number} the low 32-bits as a signed value. | |
| 157 | * @api public | |
| 158 | */ | |
| 159 | 1 | Long.prototype.getLowBits = function() { |
| 160 | 0 | return this.low_; |
| 161 | }; | |
| 162 | ||
| 163 | /** | |
| 164 | * Return the low unsigned 32-bits value. | |
| 165 | * | |
| 166 | * @return {Number} the low 32-bits as an unsigned value. | |
| 167 | * @api public | |
| 168 | */ | |
| 169 | 1 | Long.prototype.getLowBitsUnsigned = function() { |
| 170 | 0 | return (this.low_ >= 0) ? |
| 171 | this.low_ : Long.TWO_PWR_32_DBL_ + this.low_; | |
| 172 | }; | |
| 173 | ||
| 174 | /** | |
| 175 | * Returns the number of bits needed to represent the absolute value of this Long. | |
| 176 | * | |
| 177 | * @return {Number} Returns the number of bits needed to represent the absolute value of this Long. | |
| 178 | * @api public | |
| 179 | */ | |
| 180 | 1 | Long.prototype.getNumBitsAbs = function() { |
| 181 | 0 | if (this.isNegative()) { |
| 182 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 183 | 0 | return 64; |
| 184 | } else { | |
| 185 | 0 | return this.negate().getNumBitsAbs(); |
| 186 | } | |
| 187 | } else { | |
| 188 | 0 | var val = this.high_ != 0 ? this.high_ : this.low_; |
| 189 | 0 | for (var bit = 31; bit > 0; bit--) { |
| 190 | 0 | if ((val & (1 << bit)) != 0) { |
| 191 | 0 | break; |
| 192 | } | |
| 193 | } | |
| 194 | 0 | return this.high_ != 0 ? bit + 33 : bit + 1; |
| 195 | } | |
| 196 | }; | |
| 197 | ||
| 198 | /** | |
| 199 | * Return whether this value is zero. | |
| 200 | * | |
| 201 | * @return {Boolean} whether this value is zero. | |
| 202 | * @api public | |
| 203 | */ | |
| 204 | 1 | Long.prototype.isZero = function() { |
| 205 | 0 | return this.high_ == 0 && this.low_ == 0; |
| 206 | }; | |
| 207 | ||
| 208 | /** | |
| 209 | * Return whether this value is negative. | |
| 210 | * | |
| 211 | * @return {Boolean} whether this value is negative. | |
| 212 | * @api public | |
| 213 | */ | |
| 214 | 1 | Long.prototype.isNegative = function() { |
| 215 | 0 | return this.high_ < 0; |
| 216 | }; | |
| 217 | ||
| 218 | /** | |
| 219 | * Return whether this value is odd. | |
| 220 | * | |
| 221 | * @return {Boolean} whether this value is odd. | |
| 222 | * @api public | |
| 223 | */ | |
| 224 | 1 | Long.prototype.isOdd = function() { |
| 225 | 0 | return (this.low_ & 1) == 1; |
| 226 | }; | |
| 227 | ||
| 228 | /** | |
| 229 | * Return whether this Long equals the other | |
| 230 | * | |
| 231 | * @param {Long} other Long to compare against. | |
| 232 | * @return {Boolean} whether this Long equals the other | |
| 233 | * @api public | |
| 234 | */ | |
| 235 | 1 | Long.prototype.equals = function(other) { |
| 236 | 1 | return (this.high_ == other.high_) && (this.low_ == other.low_); |
| 237 | }; | |
| 238 | ||
| 239 | /** | |
| 240 | * Return whether this Long does not equal the other. | |
| 241 | * | |
| 242 | * @param {Long} other Long to compare against. | |
| 243 | * @return {Boolean} whether this Long does not equal the other. | |
| 244 | * @api public | |
| 245 | */ | |
| 246 | 1 | Long.prototype.notEquals = function(other) { |
| 247 | 0 | return (this.high_ != other.high_) || (this.low_ != other.low_); |
| 248 | }; | |
| 249 | ||
| 250 | /** | |
| 251 | * Return whether this Long is less than the other. | |
| 252 | * | |
| 253 | * @param {Long} other Long to compare against. | |
| 254 | * @return {Boolean} whether this Long is less than the other. | |
| 255 | * @api public | |
| 256 | */ | |
| 257 | 1 | Long.prototype.lessThan = function(other) { |
| 258 | 0 | return this.compare(other) < 0; |
| 259 | }; | |
| 260 | ||
| 261 | /** | |
| 262 | * Return whether this Long is less than or equal to the other. | |
| 263 | * | |
| 264 | * @param {Long} other Long to compare against. | |
| 265 | * @return {Boolean} whether this Long is less than or equal to the other. | |
| 266 | * @api public | |
| 267 | */ | |
| 268 | 1 | Long.prototype.lessThanOrEqual = function(other) { |
| 269 | 0 | return this.compare(other) <= 0; |
| 270 | }; | |
| 271 | ||
| 272 | /** | |
| 273 | * Return whether this Long is greater than the other. | |
| 274 | * | |
| 275 | * @param {Long} other Long to compare against. | |
| 276 | * @return {Boolean} whether this Long is greater than the other. | |
| 277 | * @api public | |
| 278 | */ | |
| 279 | 1 | Long.prototype.greaterThan = function(other) { |
| 280 | 0 | return this.compare(other) > 0; |
| 281 | }; | |
| 282 | ||
| 283 | /** | |
| 284 | * Return whether this Long is greater than or equal to the other. | |
| 285 | * | |
| 286 | * @param {Long} other Long to compare against. | |
| 287 | * @return {Boolean} whether this Long is greater than or equal to the other. | |
| 288 | * @api public | |
| 289 | */ | |
| 290 | 1 | Long.prototype.greaterThanOrEqual = function(other) { |
| 291 | 0 | return this.compare(other) >= 0; |
| 292 | }; | |
| 293 | ||
| 294 | /** | |
| 295 | * Compares this Long with the given one. | |
| 296 | * | |
| 297 | * @param {Long} other Long to compare against. | |
| 298 | * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. | |
| 299 | * @api public | |
| 300 | */ | |
| 301 | 1 | Long.prototype.compare = function(other) { |
| 302 | 0 | if (this.equals(other)) { |
| 303 | 0 | return 0; |
| 304 | } | |
| 305 | ||
| 306 | 0 | var thisNeg = this.isNegative(); |
| 307 | 0 | var otherNeg = other.isNegative(); |
| 308 | 0 | if (thisNeg && !otherNeg) { |
| 309 | 0 | return -1; |
| 310 | } | |
| 311 | 0 | if (!thisNeg && otherNeg) { |
| 312 | 0 | return 1; |
| 313 | } | |
| 314 | ||
| 315 | // at this point, the signs are the same, so subtraction will not overflow | |
| 316 | 0 | if (this.subtract(other).isNegative()) { |
| 317 | 0 | return -1; |
| 318 | } else { | |
| 319 | 0 | return 1; |
| 320 | } | |
| 321 | }; | |
| 322 | ||
| 323 | /** | |
| 324 | * The negation of this value. | |
| 325 | * | |
| 326 | * @return {Long} the negation of this value. | |
| 327 | * @api public | |
| 328 | */ | |
| 329 | 1 | Long.prototype.negate = function() { |
| 330 | 1 | if (this.equals(Long.MIN_VALUE)) { |
| 331 | 0 | return Long.MIN_VALUE; |
| 332 | } else { | |
| 333 | 1 | return this.not().add(Long.ONE); |
| 334 | } | |
| 335 | }; | |
| 336 | ||
| 337 | /** | |
| 338 | * Returns the sum of this and the given Long. | |
| 339 | * | |
| 340 | * @param {Long} other Long to add to this one. | |
| 341 | * @return {Long} the sum of this and the given Long. | |
| 342 | * @api public | |
| 343 | */ | |
| 344 | 1 | Long.prototype.add = function(other) { |
| 345 | // Divide each number into 4 chunks of 16 bits, and then sum the chunks. | |
| 346 | ||
| 347 | 1 | var a48 = this.high_ >>> 16; |
| 348 | 1 | var a32 = this.high_ & 0xFFFF; |
| 349 | 1 | var a16 = this.low_ >>> 16; |
| 350 | 1 | var a00 = this.low_ & 0xFFFF; |
| 351 | ||
| 352 | 1 | var b48 = other.high_ >>> 16; |
| 353 | 1 | var b32 = other.high_ & 0xFFFF; |
| 354 | 1 | var b16 = other.low_ >>> 16; |
| 355 | 1 | var b00 = other.low_ & 0xFFFF; |
| 356 | ||
| 357 | 1 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 358 | 1 | c00 += a00 + b00; |
| 359 | 1 | c16 += c00 >>> 16; |
| 360 | 1 | c00 &= 0xFFFF; |
| 361 | 1 | c16 += a16 + b16; |
| 362 | 1 | c32 += c16 >>> 16; |
| 363 | 1 | c16 &= 0xFFFF; |
| 364 | 1 | c32 += a32 + b32; |
| 365 | 1 | c48 += c32 >>> 16; |
| 366 | 1 | c32 &= 0xFFFF; |
| 367 | 1 | c48 += a48 + b48; |
| 368 | 1 | c48 &= 0xFFFF; |
| 369 | 1 | return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 370 | }; | |
| 371 | ||
| 372 | /** | |
| 373 | * Returns the difference of this and the given Long. | |
| 374 | * | |
| 375 | * @param {Long} other Long to subtract from this. | |
| 376 | * @return {Long} the difference of this and the given Long. | |
| 377 | * @api public | |
| 378 | */ | |
| 379 | 1 | Long.prototype.subtract = function(other) { |
| 380 | 0 | return this.add(other.negate()); |
| 381 | }; | |
| 382 | ||
| 383 | /** | |
| 384 | * Returns the product of this and the given Long. | |
| 385 | * | |
| 386 | * @param {Long} other Long to multiply with this. | |
| 387 | * @return {Long} the product of this and the other. | |
| 388 | * @api public | |
| 389 | */ | |
| 390 | 1 | Long.prototype.multiply = function(other) { |
| 391 | 0 | if (this.isZero()) { |
| 392 | 0 | return Long.ZERO; |
| 393 | 0 | } else if (other.isZero()) { |
| 394 | 0 | return Long.ZERO; |
| 395 | } | |
| 396 | ||
| 397 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 398 | 0 | return other.isOdd() ? Long.MIN_VALUE : Long.ZERO; |
| 399 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 400 | 0 | return this.isOdd() ? Long.MIN_VALUE : Long.ZERO; |
| 401 | } | |
| 402 | ||
| 403 | 0 | if (this.isNegative()) { |
| 404 | 0 | if (other.isNegative()) { |
| 405 | 0 | return this.negate().multiply(other.negate()); |
| 406 | } else { | |
| 407 | 0 | return this.negate().multiply(other).negate(); |
| 408 | } | |
| 409 | 0 | } else if (other.isNegative()) { |
| 410 | 0 | return this.multiply(other.negate()).negate(); |
| 411 | } | |
| 412 | ||
| 413 | // If both Longs are small, use float multiplication | |
| 414 | 0 | if (this.lessThan(Long.TWO_PWR_24_) && |
| 415 | other.lessThan(Long.TWO_PWR_24_)) { | |
| 416 | 0 | return Long.fromNumber(this.toNumber() * other.toNumber()); |
| 417 | } | |
| 418 | ||
| 419 | // Divide each Long into 4 chunks of 16 bits, and then add up 4x4 products. | |
| 420 | // We can skip products that would overflow. | |
| 421 | ||
| 422 | 0 | var a48 = this.high_ >>> 16; |
| 423 | 0 | var a32 = this.high_ & 0xFFFF; |
| 424 | 0 | var a16 = this.low_ >>> 16; |
| 425 | 0 | var a00 = this.low_ & 0xFFFF; |
| 426 | ||
| 427 | 0 | var b48 = other.high_ >>> 16; |
| 428 | 0 | var b32 = other.high_ & 0xFFFF; |
| 429 | 0 | var b16 = other.low_ >>> 16; |
| 430 | 0 | var b00 = other.low_ & 0xFFFF; |
| 431 | ||
| 432 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 433 | 0 | c00 += a00 * b00; |
| 434 | 0 | c16 += c00 >>> 16; |
| 435 | 0 | c00 &= 0xFFFF; |
| 436 | 0 | c16 += a16 * b00; |
| 437 | 0 | c32 += c16 >>> 16; |
| 438 | 0 | c16 &= 0xFFFF; |
| 439 | 0 | c16 += a00 * b16; |
| 440 | 0 | c32 += c16 >>> 16; |
| 441 | 0 | c16 &= 0xFFFF; |
| 442 | 0 | c32 += a32 * b00; |
| 443 | 0 | c48 += c32 >>> 16; |
| 444 | 0 | c32 &= 0xFFFF; |
| 445 | 0 | c32 += a16 * b16; |
| 446 | 0 | c48 += c32 >>> 16; |
| 447 | 0 | c32 &= 0xFFFF; |
| 448 | 0 | c32 += a00 * b32; |
| 449 | 0 | c48 += c32 >>> 16; |
| 450 | 0 | c32 &= 0xFFFF; |
| 451 | 0 | c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; |
| 452 | 0 | c48 &= 0xFFFF; |
| 453 | 0 | return Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 454 | }; | |
| 455 | ||
| 456 | /** | |
| 457 | * Returns this Long divided by the given one. | |
| 458 | * | |
| 459 | * @param {Long} other Long by which to divide. | |
| 460 | * @return {Long} this Long divided by the given one. | |
| 461 | * @api public | |
| 462 | */ | |
| 463 | 1 | Long.prototype.div = function(other) { |
| 464 | 0 | if (other.isZero()) { |
| 465 | 0 | throw Error('division by zero'); |
| 466 | 0 | } else if (this.isZero()) { |
| 467 | 0 | return Long.ZERO; |
| 468 | } | |
| 469 | ||
| 470 | 0 | if (this.equals(Long.MIN_VALUE)) { |
| 471 | 0 | if (other.equals(Long.ONE) || |
| 472 | other.equals(Long.NEG_ONE)) { | |
| 473 | 0 | return Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE |
| 474 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 475 | 0 | return Long.ONE; |
| 476 | } else { | |
| 477 | // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. | |
| 478 | 0 | var halfThis = this.shiftRight(1); |
| 479 | 0 | var approx = halfThis.div(other).shiftLeft(1); |
| 480 | 0 | if (approx.equals(Long.ZERO)) { |
| 481 | 0 | return other.isNegative() ? Long.ONE : Long.NEG_ONE; |
| 482 | } else { | |
| 483 | 0 | var rem = this.subtract(other.multiply(approx)); |
| 484 | 0 | var result = approx.add(rem.div(other)); |
| 485 | 0 | return result; |
| 486 | } | |
| 487 | } | |
| 488 | 0 | } else if (other.equals(Long.MIN_VALUE)) { |
| 489 | 0 | return Long.ZERO; |
| 490 | } | |
| 491 | ||
| 492 | 0 | if (this.isNegative()) { |
| 493 | 0 | if (other.isNegative()) { |
| 494 | 0 | return this.negate().div(other.negate()); |
| 495 | } else { | |
| 496 | 0 | return this.negate().div(other).negate(); |
| 497 | } | |
| 498 | 0 | } else if (other.isNegative()) { |
| 499 | 0 | return this.div(other.negate()).negate(); |
| 500 | } | |
| 501 | ||
| 502 | // Repeat the following until the remainder is less than other: find a | |
| 503 | // floating-point that approximates remainder / other *from below*, add this | |
| 504 | // into the result, and subtract it from the remainder. It is critical that | |
| 505 | // the approximate value is less than or equal to the real value so that the | |
| 506 | // remainder never becomes negative. | |
| 507 | 0 | var res = Long.ZERO; |
| 508 | 0 | var rem = this; |
| 509 | 0 | while (rem.greaterThanOrEqual(other)) { |
| 510 | // Approximate the result of division. This may be a little greater or | |
| 511 | // smaller than the actual value. | |
| 512 | 0 | var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); |
| 513 | ||
| 514 | // We will tweak the approximate result by changing it in the 48-th digit or | |
| 515 | // the smallest non-fractional digit, whichever is larger. | |
| 516 | 0 | var log2 = Math.ceil(Math.log(approx) / Math.LN2); |
| 517 | 0 | var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); |
| 518 | ||
| 519 | // Decrease the approximation until it is smaller than the remainder. Note | |
| 520 | // that if it is too large, the product overflows and is negative. | |
| 521 | 0 | var approxRes = Long.fromNumber(approx); |
| 522 | 0 | var approxRem = approxRes.multiply(other); |
| 523 | 0 | while (approxRem.isNegative() || approxRem.greaterThan(rem)) { |
| 524 | 0 | approx -= delta; |
| 525 | 0 | approxRes = Long.fromNumber(approx); |
| 526 | 0 | approxRem = approxRes.multiply(other); |
| 527 | } | |
| 528 | ||
| 529 | // We know the answer can't be zero... and actually, zero would cause | |
| 530 | // infinite recursion since we would make no progress. | |
| 531 | 0 | if (approxRes.isZero()) { |
| 532 | 0 | approxRes = Long.ONE; |
| 533 | } | |
| 534 | ||
| 535 | 0 | res = res.add(approxRes); |
| 536 | 0 | rem = rem.subtract(approxRem); |
| 537 | } | |
| 538 | 0 | return res; |
| 539 | }; | |
| 540 | ||
| 541 | /** | |
| 542 | * Returns this Long modulo the given one. | |
| 543 | * | |
| 544 | * @param {Long} other Long by which to mod. | |
| 545 | * @return {Long} this Long modulo the given one. | |
| 546 | * @api public | |
| 547 | */ | |
| 548 | 1 | Long.prototype.modulo = function(other) { |
| 549 | 0 | return this.subtract(this.div(other).multiply(other)); |
| 550 | }; | |
| 551 | ||
| 552 | /** | |
| 553 | * The bitwise-NOT of this value. | |
| 554 | * | |
| 555 | * @return {Long} the bitwise-NOT of this value. | |
| 556 | * @api public | |
| 557 | */ | |
| 558 | 1 | Long.prototype.not = function() { |
| 559 | 1 | return Long.fromBits(~this.low_, ~this.high_); |
| 560 | }; | |
| 561 | ||
| 562 | /** | |
| 563 | * Returns the bitwise-AND of this Long and the given one. | |
| 564 | * | |
| 565 | * @param {Long} other the Long with which to AND. | |
| 566 | * @return {Long} the bitwise-AND of this and the other. | |
| 567 | * @api public | |
| 568 | */ | |
| 569 | 1 | Long.prototype.and = function(other) { |
| 570 | 0 | return Long.fromBits(this.low_ & other.low_, this.high_ & other.high_); |
| 571 | }; | |
| 572 | ||
| 573 | /** | |
| 574 | * Returns the bitwise-OR of this Long and the given one. | |
| 575 | * | |
| 576 | * @param {Long} other the Long with which to OR. | |
| 577 | * @return {Long} the bitwise-OR of this and the other. | |
| 578 | * @api public | |
| 579 | */ | |
| 580 | 1 | Long.prototype.or = function(other) { |
| 581 | 0 | return Long.fromBits(this.low_ | other.low_, this.high_ | other.high_); |
| 582 | }; | |
| 583 | ||
| 584 | /** | |
| 585 | * Returns the bitwise-XOR of this Long and the given one. | |
| 586 | * | |
| 587 | * @param {Long} other the Long with which to XOR. | |
| 588 | * @return {Long} the bitwise-XOR of this and the other. | |
| 589 | * @api public | |
| 590 | */ | |
| 591 | 1 | Long.prototype.xor = function(other) { |
| 592 | 0 | return Long.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); |
| 593 | }; | |
| 594 | ||
| 595 | /** | |
| 596 | * Returns this Long with bits shifted to the left by the given amount. | |
| 597 | * | |
| 598 | * @param {Number} numBits the number of bits by which to shift. | |
| 599 | * @return {Long} this shifted to the left by the given amount. | |
| 600 | * @api public | |
| 601 | */ | |
| 602 | 1 | Long.prototype.shiftLeft = function(numBits) { |
| 603 | 0 | numBits &= 63; |
| 604 | 0 | if (numBits == 0) { |
| 605 | 0 | return this; |
| 606 | } else { | |
| 607 | 0 | var low = this.low_; |
| 608 | 0 | if (numBits < 32) { |
| 609 | 0 | var high = this.high_; |
| 610 | 0 | return Long.fromBits( |
| 611 | low << numBits, | |
| 612 | (high << numBits) | (low >>> (32 - numBits))); | |
| 613 | } else { | |
| 614 | 0 | return Long.fromBits(0, low << (numBits - 32)); |
| 615 | } | |
| 616 | } | |
| 617 | }; | |
| 618 | ||
| 619 | /** | |
| 620 | * Returns this Long with bits shifted to the right by the given amount. | |
| 621 | * | |
| 622 | * @param {Number} numBits the number of bits by which to shift. | |
| 623 | * @return {Long} this shifted to the right by the given amount. | |
| 624 | * @api public | |
| 625 | */ | |
| 626 | 1 | Long.prototype.shiftRight = function(numBits) { |
| 627 | 0 | numBits &= 63; |
| 628 | 0 | if (numBits == 0) { |
| 629 | 0 | return this; |
| 630 | } else { | |
| 631 | 0 | var high = this.high_; |
| 632 | 0 | if (numBits < 32) { |
| 633 | 0 | var low = this.low_; |
| 634 | 0 | return Long.fromBits( |
| 635 | (low >>> numBits) | (high << (32 - numBits)), | |
| 636 | high >> numBits); | |
| 637 | } else { | |
| 638 | 0 | return Long.fromBits( |
| 639 | high >> (numBits - 32), | |
| 640 | high >= 0 ? 0 : -1); | |
| 641 | } | |
| 642 | } | |
| 643 | }; | |
| 644 | ||
| 645 | /** | |
| 646 | * Returns this Long with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. | |
| 647 | * | |
| 648 | * @param {Number} numBits the number of bits by which to shift. | |
| 649 | * @return {Long} this shifted to the right by the given amount, with zeros placed into the new leading bits. | |
| 650 | * @api public | |
| 651 | */ | |
| 652 | 1 | Long.prototype.shiftRightUnsigned = function(numBits) { |
| 653 | 0 | numBits &= 63; |
| 654 | 0 | if (numBits == 0) { |
| 655 | 0 | return this; |
| 656 | } else { | |
| 657 | 0 | var high = this.high_; |
| 658 | 0 | if (numBits < 32) { |
| 659 | 0 | var low = this.low_; |
| 660 | 0 | return Long.fromBits( |
| 661 | (low >>> numBits) | (high << (32 - numBits)), | |
| 662 | high >>> numBits); | |
| 663 | 0 | } else if (numBits == 32) { |
| 664 | 0 | return Long.fromBits(high, 0); |
| 665 | } else { | |
| 666 | 0 | return Long.fromBits(high >>> (numBits - 32), 0); |
| 667 | } | |
| 668 | } | |
| 669 | }; | |
| 670 | ||
| 671 | /** | |
| 672 | * Returns a Long representing the given (32-bit) integer value. | |
| 673 | * | |
| 674 | * @param {Number} value the 32-bit integer in question. | |
| 675 | * @return {Long} the corresponding Long value. | |
| 676 | * @api public | |
| 677 | */ | |
| 678 | 1 | Long.fromInt = function(value) { |
| 679 | 4 | if (-128 <= value && value < 128) { |
| 680 | 3 | var cachedObj = Long.INT_CACHE_[value]; |
| 681 | 3 | if (cachedObj) { |
| 682 | 0 | return cachedObj; |
| 683 | } | |
| 684 | } | |
| 685 | ||
| 686 | 4 | var obj = new Long(value | 0, value < 0 ? -1 : 0); |
| 687 | 4 | if (-128 <= value && value < 128) { |
| 688 | 3 | Long.INT_CACHE_[value] = obj; |
| 689 | } | |
| 690 | 4 | return obj; |
| 691 | }; | |
| 692 | ||
| 693 | /** | |
| 694 | * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. | |
| 695 | * | |
| 696 | * @param {Number} value the number in question. | |
| 697 | * @return {Long} the corresponding Long value. | |
| 698 | * @api public | |
| 699 | */ | |
| 700 | 1 | Long.fromNumber = function(value) { |
| 701 | 3 | if (isNaN(value) || !isFinite(value)) { |
| 702 | 0 | return Long.ZERO; |
| 703 | 3 | } else if (value <= -Long.TWO_PWR_63_DBL_) { |
| 704 | 0 | return Long.MIN_VALUE; |
| 705 | 3 | } else if (value + 1 >= Long.TWO_PWR_63_DBL_) { |
| 706 | 0 | return Long.MAX_VALUE; |
| 707 | 3 | } else if (value < 0) { |
| 708 | 1 | return Long.fromNumber(-value).negate(); |
| 709 | } else { | |
| 710 | 2 | return new Long( |
| 711 | (value % Long.TWO_PWR_32_DBL_) | 0, | |
| 712 | (value / Long.TWO_PWR_32_DBL_) | 0); | |
| 713 | } | |
| 714 | }; | |
| 715 | ||
| 716 | /** | |
| 717 | * Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. | |
| 718 | * | |
| 719 | * @param {Number} lowBits the low 32-bits. | |
| 720 | * @param {Number} highBits the high 32-bits. | |
| 721 | * @return {Long} the corresponding Long value. | |
| 722 | * @api public | |
| 723 | */ | |
| 724 | 1 | Long.fromBits = function(lowBits, highBits) { |
| 725 | 4 | return new Long(lowBits, highBits); |
| 726 | }; | |
| 727 | ||
| 728 | /** | |
| 729 | * Returns a Long representation of the given string, written using the given radix. | |
| 730 | * | |
| 731 | * @param {String} str the textual representation of the Long. | |
| 732 | * @param {Number} opt_radix the radix in which the text is written. | |
| 733 | * @return {Long} the corresponding Long value. | |
| 734 | * @api public | |
| 735 | */ | |
| 736 | 1 | Long.fromString = function(str, opt_radix) { |
| 737 | 0 | if (str.length == 0) { |
| 738 | 0 | throw Error('number format error: empty string'); |
| 739 | } | |
| 740 | ||
| 741 | 0 | var radix = opt_radix || 10; |
| 742 | 0 | if (radix < 2 || 36 < radix) { |
| 743 | 0 | throw Error('radix out of range: ' + radix); |
| 744 | } | |
| 745 | ||
| 746 | 0 | if (str.charAt(0) == '-') { |
| 747 | 0 | return Long.fromString(str.substring(1), radix).negate(); |
| 748 | 0 | } else if (str.indexOf('-') >= 0) { |
| 749 | 0 | throw Error('number format error: interior "-" character: ' + str); |
| 750 | } | |
| 751 | ||
| 752 | // Do several (8) digits each time through the loop, so as to | |
| 753 | // minimize the calls to the very expensive emulated div. | |
| 754 | 0 | var radixToPower = Long.fromNumber(Math.pow(radix, 8)); |
| 755 | ||
| 756 | 0 | var result = Long.ZERO; |
| 757 | 0 | for (var i = 0; i < str.length; i += 8) { |
| 758 | 0 | var size = Math.min(8, str.length - i); |
| 759 | 0 | var value = parseInt(str.substring(i, i + size), radix); |
| 760 | 0 | if (size < 8) { |
| 761 | 0 | var power = Long.fromNumber(Math.pow(radix, size)); |
| 762 | 0 | result = result.multiply(power).add(Long.fromNumber(value)); |
| 763 | } else { | |
| 764 | 0 | result = result.multiply(radixToPower); |
| 765 | 0 | result = result.add(Long.fromNumber(value)); |
| 766 | } | |
| 767 | } | |
| 768 | 0 | return result; |
| 769 | }; | |
| 770 | ||
| 771 | // NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the | |
| 772 | // from* methods on which they depend. | |
| 773 | ||
| 774 | ||
| 775 | /** | |
| 776 | * A cache of the Long representations of small integer values. | |
| 777 | * @type {Object} | |
| 778 | * @api private | |
| 779 | */ | |
| 780 | 1 | Long.INT_CACHE_ = {}; |
| 781 | ||
| 782 | // NOTE: the compiler should inline these constant values below and then remove | |
| 783 | // these variables, so there should be no runtime penalty for these. | |
| 784 | ||
| 785 | /** | |
| 786 | * Number used repeated below in calculations. This must appear before the | |
| 787 | * first call to any from* function below. | |
| 788 | * @type {number} | |
| 789 | * @api private | |
| 790 | */ | |
| 791 | 1 | Long.TWO_PWR_16_DBL_ = 1 << 16; |
| 792 | ||
| 793 | /** | |
| 794 | * @type {number} | |
| 795 | * @api private | |
| 796 | */ | |
| 797 | 1 | Long.TWO_PWR_24_DBL_ = 1 << 24; |
| 798 | ||
| 799 | /** | |
| 800 | * @type {number} | |
| 801 | * @api private | |
| 802 | */ | |
| 803 | 1 | Long.TWO_PWR_32_DBL_ = Long.TWO_PWR_16_DBL_ * Long.TWO_PWR_16_DBL_; |
| 804 | ||
| 805 | /** | |
| 806 | * @type {number} | |
| 807 | * @api private | |
| 808 | */ | |
| 809 | 1 | Long.TWO_PWR_31_DBL_ = Long.TWO_PWR_32_DBL_ / 2; |
| 810 | ||
| 811 | /** | |
| 812 | * @type {number} | |
| 813 | * @api private | |
| 814 | */ | |
| 815 | 1 | Long.TWO_PWR_48_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_16_DBL_; |
| 816 | ||
| 817 | /** | |
| 818 | * @type {number} | |
| 819 | * @api private | |
| 820 | */ | |
| 821 | 1 | Long.TWO_PWR_64_DBL_ = Long.TWO_PWR_32_DBL_ * Long.TWO_PWR_32_DBL_; |
| 822 | ||
| 823 | /** | |
| 824 | * @type {number} | |
| 825 | * @api private | |
| 826 | */ | |
| 827 | 1 | Long.TWO_PWR_63_DBL_ = Long.TWO_PWR_64_DBL_ / 2; |
| 828 | ||
| 829 | /** @type {Long} */ | |
| 830 | 1 | Long.ZERO = Long.fromInt(0); |
| 831 | ||
| 832 | /** @type {Long} */ | |
| 833 | 1 | Long.ONE = Long.fromInt(1); |
| 834 | ||
| 835 | /** @type {Long} */ | |
| 836 | 1 | Long.NEG_ONE = Long.fromInt(-1); |
| 837 | ||
| 838 | /** @type {Long} */ | |
| 839 | 1 | Long.MAX_VALUE = |
| 840 | Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); | |
| 841 | ||
| 842 | /** @type {Long} */ | |
| 843 | 1 | Long.MIN_VALUE = Long.fromBits(0, 0x80000000 | 0); |
| 844 | ||
| 845 | /** | |
| 846 | * @type {Long} | |
| 847 | * @api private | |
| 848 | */ | |
| 849 | 1 | Long.TWO_PWR_24_ = Long.fromInt(1 << 24); |
| 850 | ||
| 851 | /** | |
| 852 | * Expose. | |
| 853 | */ | |
| 854 | 1 | exports.Long = Long; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON MaxKey type. | |
| 3 | * | |
| 4 | * @class Represents the BSON MaxKey type. | |
| 5 | * @return {MaxKey} | |
| 6 | */ | |
| 7 | 1 | function MaxKey() { |
| 8 | 0 | if(!(this instanceof MaxKey)) return new MaxKey(); |
| 9 | ||
| 10 | 0 | this._bsontype = 'MaxKey'; |
| 11 | } | |
| 12 | ||
| 13 | 1 | exports.MaxKey = MaxKey; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON MinKey type. | |
| 3 | * | |
| 4 | * @class Represents the BSON MinKey type. | |
| 5 | * @return {MinKey} | |
| 6 | */ | |
| 7 | 1 | function MinKey() { |
| 8 | 0 | if(!(this instanceof MinKey)) return new MinKey(); |
| 9 | ||
| 10 | 0 | this._bsontype = 'MinKey'; |
| 11 | } | |
| 12 | ||
| 13 | 1 | exports.MinKey = MinKey; |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | 1 | var BinaryParser = require('./binary_parser').BinaryParser; |
| 5 | ||
| 6 | /** | |
| 7 | * Machine id. | |
| 8 | * | |
| 9 | * Create a random 3-byte value (i.e. unique for this | |
| 10 | * process). Other drivers use a md5 of the machine id here, but | |
| 11 | * that would mean an asyc call to gethostname, so we don't bother. | |
| 12 | */ | |
| 13 | 1 | var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10); |
| 14 | ||
| 15 | // Regular expression that checks for hex value | |
| 16 | 1 | var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$"); |
| 17 | ||
| 18 | /** | |
| 19 | * Create a new ObjectID instance | |
| 20 | * | |
| 21 | * @class Represents the BSON ObjectID type | |
| 22 | * @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number. | |
| 23 | * @return {Object} instance of ObjectID. | |
| 24 | */ | |
| 25 | 1 | var ObjectID = function ObjectID(id) { |
| 26 | 0 | if(!(this instanceof ObjectID)) return new ObjectID(id); |
| 27 | ||
| 28 | 0 | this._bsontype = 'ObjectID'; |
| 29 | 0 | var __id = null; |
| 30 | ||
| 31 | // Throw an error if it's not a valid setup | |
| 32 | 0 | if(id != null && 'number' != typeof id && (id.length != 12 && id.length != 24)) |
| 33 | 0 | throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); |
| 34 | ||
| 35 | // Generate id based on the input | |
| 36 | 0 | if(id == null || typeof id == 'number') { |
| 37 | // convert to 12 byte binary string | |
| 38 | 0 | this.id = this.generate(id); |
| 39 | 0 | } else if(id != null && id.length === 12) { |
| 40 | // assume 12 byte string | |
| 41 | 0 | this.id = id; |
| 42 | 0 | } else if(checkForHexRegExp.test(id)) { |
| 43 | 0 | return ObjectID.createFromHexString(id); |
| 44 | } else { | |
| 45 | 0 | throw new Error("Value passed in is not a valid 24 character hex string"); |
| 46 | } | |
| 47 | ||
| 48 | 0 | if(ObjectID.cacheHexString) this.__id = this.toHexString(); |
| 49 | }; | |
| 50 | ||
| 51 | // Allow usage of ObjectId aswell as ObjectID | |
| 52 | 1 | var ObjectId = ObjectID; |
| 53 | ||
| 54 | /** | |
| 55 | * Return the ObjectID id as a 24 byte hex string representation | |
| 56 | * | |
| 57 | * @return {String} return the 24 byte hex string representation. | |
| 58 | * @api public | |
| 59 | */ | |
| 60 | 1 | ObjectID.prototype.toHexString = function() { |
| 61 | 0 | if(ObjectID.cacheHexString && this.__id) return this.__id; |
| 62 | ||
| 63 | 0 | var hexString = '' |
| 64 | , number | |
| 65 | , value; | |
| 66 | ||
| 67 | 0 | for (var index = 0, len = this.id.length; index < len; index++) { |
| 68 | 0 | value = BinaryParser.toByte(this.id[index]); |
| 69 | 0 | number = value <= 15 |
| 70 | ? '0' + value.toString(16) | |
| 71 | : value.toString(16); | |
| 72 | 0 | hexString = hexString + number; |
| 73 | } | |
| 74 | ||
| 75 | 0 | if(ObjectID.cacheHexString) this.__id = hexString; |
| 76 | 0 | return hexString; |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Update the ObjectID index used in generating new ObjectID's on the driver | |
| 81 | * | |
| 82 | * @return {Number} returns next index value. | |
| 83 | * @api private | |
| 84 | */ | |
| 85 | 1 | ObjectID.prototype.get_inc = function() { |
| 86 | 0 | return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF; |
| 87 | }; | |
| 88 | ||
| 89 | /** | |
| 90 | * Update the ObjectID index used in generating new ObjectID's on the driver | |
| 91 | * | |
| 92 | * @return {Number} returns next index value. | |
| 93 | * @api private | |
| 94 | */ | |
| 95 | 1 | ObjectID.prototype.getInc = function() { |
| 96 | 0 | return this.get_inc(); |
| 97 | }; | |
| 98 | ||
| 99 | /** | |
| 100 | * Generate a 12 byte id string used in ObjectID's | |
| 101 | * | |
| 102 | * @param {Number} [time] optional parameter allowing to pass in a second based timestamp. | |
| 103 | * @return {String} return the 12 byte id binary string. | |
| 104 | * @api private | |
| 105 | */ | |
| 106 | 1 | ObjectID.prototype.generate = function(time) { |
| 107 | 0 | if ('number' != typeof time) { |
| 108 | 0 | time = parseInt(Date.now()/1000,10); |
| 109 | } | |
| 110 | ||
| 111 | 0 | var time4Bytes = BinaryParser.encodeInt(time, 32, true, true); |
| 112 | /* for time-based ObjectID the bytes following the time will be zeroed */ | |
| 113 | 0 | var machine3Bytes = BinaryParser.encodeInt(MACHINE_ID, 24, false); |
| 114 | 0 | var pid2Bytes = BinaryParser.fromShort(typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid); |
| 115 | 0 | var index3Bytes = BinaryParser.encodeInt(this.get_inc(), 24, false, true); |
| 116 | ||
| 117 | 0 | return time4Bytes + machine3Bytes + pid2Bytes + index3Bytes; |
| 118 | }; | |
| 119 | ||
| 120 | /** | |
| 121 | * Converts the id into a 24 byte hex string for printing | |
| 122 | * | |
| 123 | * @return {String} return the 24 byte hex string representation. | |
| 124 | * @api private | |
| 125 | */ | |
| 126 | 1 | ObjectID.prototype.toString = function() { |
| 127 | 0 | return this.toHexString(); |
| 128 | }; | |
| 129 | ||
| 130 | /** | |
| 131 | * Converts to a string representation of this Id. | |
| 132 | * | |
| 133 | * @return {String} return the 24 byte hex string representation. | |
| 134 | * @api private | |
| 135 | */ | |
| 136 | 1 | ObjectID.prototype.inspect = ObjectID.prototype.toString; |
| 137 | ||
| 138 | /** | |
| 139 | * Converts to its JSON representation. | |
| 140 | * | |
| 141 | * @return {String} return the 24 byte hex string representation. | |
| 142 | * @api private | |
| 143 | */ | |
| 144 | 1 | ObjectID.prototype.toJSON = function() { |
| 145 | 0 | return this.toHexString(); |
| 146 | }; | |
| 147 | ||
| 148 | /** | |
| 149 | * Compares the equality of this ObjectID with `otherID`. | |
| 150 | * | |
| 151 | * @param {Object} otherID ObjectID instance to compare against. | |
| 152 | * @return {Bool} the result of comparing two ObjectID's | |
| 153 | * @api public | |
| 154 | */ | |
| 155 | 1 | ObjectID.prototype.equals = function equals (otherID) { |
| 156 | 0 | var id = (otherID instanceof ObjectID || otherID.toHexString) |
| 157 | ? otherID.id | |
| 158 | : ObjectID.createFromHexString(otherID).id; | |
| 159 | ||
| 160 | 0 | return this.id === id; |
| 161 | } | |
| 162 | ||
| 163 | /** | |
| 164 | * Returns the generation date (accurate up to the second) that this ID was generated. | |
| 165 | * | |
| 166 | * @return {Date} the generation date | |
| 167 | * @api public | |
| 168 | */ | |
| 169 | 1 | ObjectID.prototype.getTimestamp = function() { |
| 170 | 0 | var timestamp = new Date(); |
| 171 | 0 | timestamp.setTime(Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)) * 1000); |
| 172 | 0 | return timestamp; |
| 173 | } | |
| 174 | ||
| 175 | /** | |
| 176 | * @ignore | |
| 177 | * @api private | |
| 178 | */ | |
| 179 | 1 | ObjectID.index = parseInt(Math.random() * 0xFFFFFF, 10); |
| 180 | ||
| 181 | 1 | ObjectID.createPk = function createPk () { |
| 182 | 0 | return new ObjectID(); |
| 183 | }; | |
| 184 | ||
| 185 | /** | |
| 186 | * Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID. | |
| 187 | * | |
| 188 | * @param {Number} time an integer number representing a number of seconds. | |
| 189 | * @return {ObjectID} return the created ObjectID | |
| 190 | * @api public | |
| 191 | */ | |
| 192 | 1 | ObjectID.createFromTime = function createFromTime (time) { |
| 193 | 0 | var id = BinaryParser.encodeInt(time, 32, true, true) + |
| 194 | BinaryParser.encodeInt(0, 64, true, true); | |
| 195 | 0 | return new ObjectID(id); |
| 196 | }; | |
| 197 | ||
| 198 | /** | |
| 199 | * Creates an ObjectID from a hex string representation of an ObjectID. | |
| 200 | * | |
| 201 | * @param {String} hexString create a ObjectID from a passed in 24 byte hexstring. | |
| 202 | * @return {ObjectID} return the created ObjectID | |
| 203 | * @api public | |
| 204 | */ | |
| 205 | 1 | ObjectID.createFromHexString = function createFromHexString (hexString) { |
| 206 | // Throw an error if it's not a valid setup | |
| 207 | 0 | if(typeof hexString === 'undefined' || hexString != null && hexString.length != 24) |
| 208 | 0 | throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); |
| 209 | ||
| 210 | 0 | var len = hexString.length; |
| 211 | ||
| 212 | 0 | if(len > 12*2) { |
| 213 | 0 | throw new Error('Id cannot be longer than 12 bytes'); |
| 214 | } | |
| 215 | ||
| 216 | 0 | var result = '' |
| 217 | , string | |
| 218 | , number; | |
| 219 | ||
| 220 | 0 | for (var index = 0; index < len; index += 2) { |
| 221 | 0 | string = hexString.substr(index, 2); |
| 222 | 0 | number = parseInt(string, 16); |
| 223 | 0 | result += BinaryParser.fromByte(number); |
| 224 | } | |
| 225 | ||
| 226 | 0 | return new ObjectID(result, hexString); |
| 227 | }; | |
| 228 | ||
| 229 | /** | |
| 230 | * @ignore | |
| 231 | */ | |
| 232 | 1 | Object.defineProperty(ObjectID.prototype, "generationTime", { |
| 233 | enumerable: true | |
| 234 | , get: function () { | |
| 235 | 0 | return Math.floor(BinaryParser.decodeInt(this.id.substring(0,4), 32, true, true)); |
| 236 | } | |
| 237 | , set: function (value) { | |
| 238 | 0 | var value = BinaryParser.encodeInt(value, 32, true, true); |
| 239 | 0 | this.id = value + this.id.substr(4); |
| 240 | // delete this.__id; | |
| 241 | 0 | this.toHexString(); |
| 242 | } | |
| 243 | }); | |
| 244 | ||
| 245 | /** | |
| 246 | * Expose. | |
| 247 | */ | |
| 248 | 1 | exports.ObjectID = ObjectID; |
| 249 | 1 | exports.ObjectId = ObjectID; |
| 250 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * A class representation of the BSON Symbol type. | |
| 3 | * | |
| 4 | * @class Represents the BSON Symbol type. | |
| 5 | * @param {String} value the string representing the symbol. | |
| 6 | * @return {Symbol} | |
| 7 | */ | |
| 8 | 1 | function Symbol(value) { |
| 9 | 0 | if(!(this instanceof Symbol)) return new Symbol(value); |
| 10 | 0 | this._bsontype = 'Symbol'; |
| 11 | 0 | this.value = value; |
| 12 | } | |
| 13 | ||
| 14 | /** | |
| 15 | * Access the wrapped string value. | |
| 16 | * | |
| 17 | * @return {String} returns the wrapped string. | |
| 18 | * @api public | |
| 19 | */ | |
| 20 | 1 | Symbol.prototype.valueOf = function() { |
| 21 | 0 | return this.value; |
| 22 | }; | |
| 23 | ||
| 24 | /** | |
| 25 | * @ignore | |
| 26 | * @api private | |
| 27 | */ | |
| 28 | 1 | Symbol.prototype.toString = function() { |
| 29 | 0 | return this.value; |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * @ignore | |
| 34 | * @api private | |
| 35 | */ | |
| 36 | 1 | Symbol.prototype.inspect = function() { |
| 37 | 0 | return this.value; |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * @ignore | |
| 42 | * @api private | |
| 43 | */ | |
| 44 | 1 | Symbol.prototype.toJSON = function() { |
| 45 | 0 | return this.value; |
| 46 | } | |
| 47 | ||
| 48 | 1 | exports.Symbol = Symbol; |
| Line | Hits | Source |
|---|---|---|
| 1 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 2 | // you may not use this file except in compliance with the License. | |
| 3 | // You may obtain a copy of the License at | |
| 4 | // | |
| 5 | // http://www.apache.org/licenses/LICENSE-2.0 | |
| 6 | // | |
| 7 | // Unless required by applicable law or agreed to in writing, software | |
| 8 | // distributed under the License is distributed on an "AS IS" BASIS, | |
| 9 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 10 | // See the License for the specific language governing permissions and | |
| 11 | // limitations under the License. | |
| 12 | // | |
| 13 | // Copyright 2009 Google Inc. All Rights Reserved | |
| 14 | ||
| 15 | /** | |
| 16 | * Defines a Timestamp class for representing a 64-bit two's-complement | |
| 17 | * integer value, which faithfully simulates the behavior of a Java "Timestamp". This | |
| 18 | * implementation is derived from TimestampLib in GWT. | |
| 19 | * | |
| 20 | * Constructs a 64-bit two's-complement integer, given its low and high 32-bit | |
| 21 | * values as *signed* integers. See the from* functions below for more | |
| 22 | * convenient ways of constructing Timestamps. | |
| 23 | * | |
| 24 | * The internal representation of a Timestamp is the two given signed, 32-bit values. | |
| 25 | * We use 32-bit pieces because these are the size of integers on which | |
| 26 | * Javascript performs bit-operations. For operations like addition and | |
| 27 | * multiplication, we split each number into 16-bit pieces, which can easily be | |
| 28 | * multiplied within Javascript's floating-point representation without overflow | |
| 29 | * or change in sign. | |
| 30 | * | |
| 31 | * In the algorithms below, we frequently reduce the negative case to the | |
| 32 | * positive case by negating the input(s) and then post-processing the result. | |
| 33 | * Note that we must ALWAYS check specially whether those values are MIN_VALUE | |
| 34 | * (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as | |
| 35 | * a positive number, it overflows back into a negative). Not handling this | |
| 36 | * case would often result in infinite recursion. | |
| 37 | * | |
| 38 | * @class Represents the BSON Timestamp type. | |
| 39 | * @param {Number} low the low (signed) 32 bits of the Timestamp. | |
| 40 | * @param {Number} high the high (signed) 32 bits of the Timestamp. | |
| 41 | */ | |
| 42 | 1 | function Timestamp(low, high) { |
| 43 | 6 | if(!(this instanceof Timestamp)) return new Timestamp(low, high); |
| 44 | 6 | this._bsontype = 'Timestamp'; |
| 45 | /** | |
| 46 | * @type {number} | |
| 47 | * @api private | |
| 48 | */ | |
| 49 | 6 | this.low_ = low | 0; // force into 32 signed bits. |
| 50 | ||
| 51 | /** | |
| 52 | * @type {number} | |
| 53 | * @api private | |
| 54 | */ | |
| 55 | 6 | this.high_ = high | 0; // force into 32 signed bits. |
| 56 | }; | |
| 57 | ||
| 58 | /** | |
| 59 | * Return the int value. | |
| 60 | * | |
| 61 | * @return {Number} the value, assuming it is a 32-bit integer. | |
| 62 | * @api public | |
| 63 | */ | |
| 64 | 1 | Timestamp.prototype.toInt = function() { |
| 65 | 0 | return this.low_; |
| 66 | }; | |
| 67 | ||
| 68 | /** | |
| 69 | * Return the Number value. | |
| 70 | * | |
| 71 | * @return {Number} the closest floating-point representation to this value. | |
| 72 | * @api public | |
| 73 | */ | |
| 74 | 1 | Timestamp.prototype.toNumber = function() { |
| 75 | 0 | return this.high_ * Timestamp.TWO_PWR_32_DBL_ + |
| 76 | this.getLowBitsUnsigned(); | |
| 77 | }; | |
| 78 | ||
| 79 | /** | |
| 80 | * Return the JSON value. | |
| 81 | * | |
| 82 | * @return {String} the JSON representation. | |
| 83 | * @api public | |
| 84 | */ | |
| 85 | 1 | Timestamp.prototype.toJSON = function() { |
| 86 | 0 | return this.toString(); |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Return the String value. | |
| 91 | * | |
| 92 | * @param {Number} [opt_radix] the radix in which the text should be written. | |
| 93 | * @return {String} the textual representation of this value. | |
| 94 | * @api public | |
| 95 | */ | |
| 96 | 1 | Timestamp.prototype.toString = function(opt_radix) { |
| 97 | 0 | var radix = opt_radix || 10; |
| 98 | 0 | if (radix < 2 || 36 < radix) { |
| 99 | 0 | throw Error('radix out of range: ' + radix); |
| 100 | } | |
| 101 | ||
| 102 | 0 | if (this.isZero()) { |
| 103 | 0 | return '0'; |
| 104 | } | |
| 105 | ||
| 106 | 0 | if (this.isNegative()) { |
| 107 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 108 | // We need to change the Timestamp value before it can be negated, so we remove | |
| 109 | // the bottom-most digit in this base and then recurse to do the rest. | |
| 110 | 0 | var radixTimestamp = Timestamp.fromNumber(radix); |
| 111 | 0 | var div = this.div(radixTimestamp); |
| 112 | 0 | var rem = div.multiply(radixTimestamp).subtract(this); |
| 113 | 0 | return div.toString(radix) + rem.toInt().toString(radix); |
| 114 | } else { | |
| 115 | 0 | return '-' + this.negate().toString(radix); |
| 116 | } | |
| 117 | } | |
| 118 | ||
| 119 | // Do several (6) digits each time through the loop, so as to | |
| 120 | // minimize the calls to the very expensive emulated div. | |
| 121 | 0 | var radixToPower = Timestamp.fromNumber(Math.pow(radix, 6)); |
| 122 | ||
| 123 | 0 | var rem = this; |
| 124 | 0 | var result = ''; |
| 125 | 0 | while (true) { |
| 126 | 0 | var remDiv = rem.div(radixToPower); |
| 127 | 0 | var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt(); |
| 128 | 0 | var digits = intval.toString(radix); |
| 129 | ||
| 130 | 0 | rem = remDiv; |
| 131 | 0 | if (rem.isZero()) { |
| 132 | 0 | return digits + result; |
| 133 | } else { | |
| 134 | 0 | while (digits.length < 6) { |
| 135 | 0 | digits = '0' + digits; |
| 136 | } | |
| 137 | 0 | result = '' + digits + result; |
| 138 | } | |
| 139 | } | |
| 140 | }; | |
| 141 | ||
| 142 | /** | |
| 143 | * Return the high 32-bits value. | |
| 144 | * | |
| 145 | * @return {Number} the high 32-bits as a signed value. | |
| 146 | * @api public | |
| 147 | */ | |
| 148 | 1 | Timestamp.prototype.getHighBits = function() { |
| 149 | 0 | return this.high_; |
| 150 | }; | |
| 151 | ||
| 152 | /** | |
| 153 | * Return the low 32-bits value. | |
| 154 | * | |
| 155 | * @return {Number} the low 32-bits as a signed value. | |
| 156 | * @api public | |
| 157 | */ | |
| 158 | 1 | Timestamp.prototype.getLowBits = function() { |
| 159 | 0 | return this.low_; |
| 160 | }; | |
| 161 | ||
| 162 | /** | |
| 163 | * Return the low unsigned 32-bits value. | |
| 164 | * | |
| 165 | * @return {Number} the low 32-bits as an unsigned value. | |
| 166 | * @api public | |
| 167 | */ | |
| 168 | 1 | Timestamp.prototype.getLowBitsUnsigned = function() { |
| 169 | 0 | return (this.low_ >= 0) ? |
| 170 | this.low_ : Timestamp.TWO_PWR_32_DBL_ + this.low_; | |
| 171 | }; | |
| 172 | ||
| 173 | /** | |
| 174 | * Returns the number of bits needed to represent the absolute value of this Timestamp. | |
| 175 | * | |
| 176 | * @return {Number} Returns the number of bits needed to represent the absolute value of this Timestamp. | |
| 177 | * @api public | |
| 178 | */ | |
| 179 | 1 | Timestamp.prototype.getNumBitsAbs = function() { |
| 180 | 0 | if (this.isNegative()) { |
| 181 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 182 | 0 | return 64; |
| 183 | } else { | |
| 184 | 0 | return this.negate().getNumBitsAbs(); |
| 185 | } | |
| 186 | } else { | |
| 187 | 0 | var val = this.high_ != 0 ? this.high_ : this.low_; |
| 188 | 0 | for (var bit = 31; bit > 0; bit--) { |
| 189 | 0 | if ((val & (1 << bit)) != 0) { |
| 190 | 0 | break; |
| 191 | } | |
| 192 | } | |
| 193 | 0 | return this.high_ != 0 ? bit + 33 : bit + 1; |
| 194 | } | |
| 195 | }; | |
| 196 | ||
| 197 | /** | |
| 198 | * Return whether this value is zero. | |
| 199 | * | |
| 200 | * @return {Boolean} whether this value is zero. | |
| 201 | * @api public | |
| 202 | */ | |
| 203 | 1 | Timestamp.prototype.isZero = function() { |
| 204 | 0 | return this.high_ == 0 && this.low_ == 0; |
| 205 | }; | |
| 206 | ||
| 207 | /** | |
| 208 | * Return whether this value is negative. | |
| 209 | * | |
| 210 | * @return {Boolean} whether this value is negative. | |
| 211 | * @api public | |
| 212 | */ | |
| 213 | 1 | Timestamp.prototype.isNegative = function() { |
| 214 | 0 | return this.high_ < 0; |
| 215 | }; | |
| 216 | ||
| 217 | /** | |
| 218 | * Return whether this value is odd. | |
| 219 | * | |
| 220 | * @return {Boolean} whether this value is odd. | |
| 221 | * @api public | |
| 222 | */ | |
| 223 | 1 | Timestamp.prototype.isOdd = function() { |
| 224 | 0 | return (this.low_ & 1) == 1; |
| 225 | }; | |
| 226 | ||
| 227 | /** | |
| 228 | * Return whether this Timestamp equals the other | |
| 229 | * | |
| 230 | * @param {Timestamp} other Timestamp to compare against. | |
| 231 | * @return {Boolean} whether this Timestamp equals the other | |
| 232 | * @api public | |
| 233 | */ | |
| 234 | 1 | Timestamp.prototype.equals = function(other) { |
| 235 | 0 | return (this.high_ == other.high_) && (this.low_ == other.low_); |
| 236 | }; | |
| 237 | ||
| 238 | /** | |
| 239 | * Return whether this Timestamp does not equal the other. | |
| 240 | * | |
| 241 | * @param {Timestamp} other Timestamp to compare against. | |
| 242 | * @return {Boolean} whether this Timestamp does not equal the other. | |
| 243 | * @api public | |
| 244 | */ | |
| 245 | 1 | Timestamp.prototype.notEquals = function(other) { |
| 246 | 0 | return (this.high_ != other.high_) || (this.low_ != other.low_); |
| 247 | }; | |
| 248 | ||
| 249 | /** | |
| 250 | * Return whether this Timestamp is less than the other. | |
| 251 | * | |
| 252 | * @param {Timestamp} other Timestamp to compare against. | |
| 253 | * @return {Boolean} whether this Timestamp is less than the other. | |
| 254 | * @api public | |
| 255 | */ | |
| 256 | 1 | Timestamp.prototype.lessThan = function(other) { |
| 257 | 0 | return this.compare(other) < 0; |
| 258 | }; | |
| 259 | ||
| 260 | /** | |
| 261 | * Return whether this Timestamp is less than or equal to the other. | |
| 262 | * | |
| 263 | * @param {Timestamp} other Timestamp to compare against. | |
| 264 | * @return {Boolean} whether this Timestamp is less than or equal to the other. | |
| 265 | * @api public | |
| 266 | */ | |
| 267 | 1 | Timestamp.prototype.lessThanOrEqual = function(other) { |
| 268 | 0 | return this.compare(other) <= 0; |
| 269 | }; | |
| 270 | ||
| 271 | /** | |
| 272 | * Return whether this Timestamp is greater than the other. | |
| 273 | * | |
| 274 | * @param {Timestamp} other Timestamp to compare against. | |
| 275 | * @return {Boolean} whether this Timestamp is greater than the other. | |
| 276 | * @api public | |
| 277 | */ | |
| 278 | 1 | Timestamp.prototype.greaterThan = function(other) { |
| 279 | 0 | return this.compare(other) > 0; |
| 280 | }; | |
| 281 | ||
| 282 | /** | |
| 283 | * Return whether this Timestamp is greater than or equal to the other. | |
| 284 | * | |
| 285 | * @param {Timestamp} other Timestamp to compare against. | |
| 286 | * @return {Boolean} whether this Timestamp is greater than or equal to the other. | |
| 287 | * @api public | |
| 288 | */ | |
| 289 | 1 | Timestamp.prototype.greaterThanOrEqual = function(other) { |
| 290 | 0 | return this.compare(other) >= 0; |
| 291 | }; | |
| 292 | ||
| 293 | /** | |
| 294 | * Compares this Timestamp with the given one. | |
| 295 | * | |
| 296 | * @param {Timestamp} other Timestamp to compare against. | |
| 297 | * @return {Boolean} 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater. | |
| 298 | * @api public | |
| 299 | */ | |
| 300 | 1 | Timestamp.prototype.compare = function(other) { |
| 301 | 0 | if (this.equals(other)) { |
| 302 | 0 | return 0; |
| 303 | } | |
| 304 | ||
| 305 | 0 | var thisNeg = this.isNegative(); |
| 306 | 0 | var otherNeg = other.isNegative(); |
| 307 | 0 | if (thisNeg && !otherNeg) { |
| 308 | 0 | return -1; |
| 309 | } | |
| 310 | 0 | if (!thisNeg && otherNeg) { |
| 311 | 0 | return 1; |
| 312 | } | |
| 313 | ||
| 314 | // at this point, the signs are the same, so subtraction will not overflow | |
| 315 | 0 | if (this.subtract(other).isNegative()) { |
| 316 | 0 | return -1; |
| 317 | } else { | |
| 318 | 0 | return 1; |
| 319 | } | |
| 320 | }; | |
| 321 | ||
| 322 | /** | |
| 323 | * The negation of this value. | |
| 324 | * | |
| 325 | * @return {Timestamp} the negation of this value. | |
| 326 | * @api public | |
| 327 | */ | |
| 328 | 1 | Timestamp.prototype.negate = function() { |
| 329 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 330 | 0 | return Timestamp.MIN_VALUE; |
| 331 | } else { | |
| 332 | 0 | return this.not().add(Timestamp.ONE); |
| 333 | } | |
| 334 | }; | |
| 335 | ||
| 336 | /** | |
| 337 | * Returns the sum of this and the given Timestamp. | |
| 338 | * | |
| 339 | * @param {Timestamp} other Timestamp to add to this one. | |
| 340 | * @return {Timestamp} the sum of this and the given Timestamp. | |
| 341 | * @api public | |
| 342 | */ | |
| 343 | 1 | Timestamp.prototype.add = function(other) { |
| 344 | // Divide each number into 4 chunks of 16 bits, and then sum the chunks. | |
| 345 | ||
| 346 | 0 | var a48 = this.high_ >>> 16; |
| 347 | 0 | var a32 = this.high_ & 0xFFFF; |
| 348 | 0 | var a16 = this.low_ >>> 16; |
| 349 | 0 | var a00 = this.low_ & 0xFFFF; |
| 350 | ||
| 351 | 0 | var b48 = other.high_ >>> 16; |
| 352 | 0 | var b32 = other.high_ & 0xFFFF; |
| 353 | 0 | var b16 = other.low_ >>> 16; |
| 354 | 0 | var b00 = other.low_ & 0xFFFF; |
| 355 | ||
| 356 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 357 | 0 | c00 += a00 + b00; |
| 358 | 0 | c16 += c00 >>> 16; |
| 359 | 0 | c00 &= 0xFFFF; |
| 360 | 0 | c16 += a16 + b16; |
| 361 | 0 | c32 += c16 >>> 16; |
| 362 | 0 | c16 &= 0xFFFF; |
| 363 | 0 | c32 += a32 + b32; |
| 364 | 0 | c48 += c32 >>> 16; |
| 365 | 0 | c32 &= 0xFFFF; |
| 366 | 0 | c48 += a48 + b48; |
| 367 | 0 | c48 &= 0xFFFF; |
| 368 | 0 | return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 369 | }; | |
| 370 | ||
| 371 | /** | |
| 372 | * Returns the difference of this and the given Timestamp. | |
| 373 | * | |
| 374 | * @param {Timestamp} other Timestamp to subtract from this. | |
| 375 | * @return {Timestamp} the difference of this and the given Timestamp. | |
| 376 | * @api public | |
| 377 | */ | |
| 378 | 1 | Timestamp.prototype.subtract = function(other) { |
| 379 | 0 | return this.add(other.negate()); |
| 380 | }; | |
| 381 | ||
| 382 | /** | |
| 383 | * Returns the product of this and the given Timestamp. | |
| 384 | * | |
| 385 | * @param {Timestamp} other Timestamp to multiply with this. | |
| 386 | * @return {Timestamp} the product of this and the other. | |
| 387 | * @api public | |
| 388 | */ | |
| 389 | 1 | Timestamp.prototype.multiply = function(other) { |
| 390 | 0 | if (this.isZero()) { |
| 391 | 0 | return Timestamp.ZERO; |
| 392 | 0 | } else if (other.isZero()) { |
| 393 | 0 | return Timestamp.ZERO; |
| 394 | } | |
| 395 | ||
| 396 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 397 | 0 | return other.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; |
| 398 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 399 | 0 | return this.isOdd() ? Timestamp.MIN_VALUE : Timestamp.ZERO; |
| 400 | } | |
| 401 | ||
| 402 | 0 | if (this.isNegative()) { |
| 403 | 0 | if (other.isNegative()) { |
| 404 | 0 | return this.negate().multiply(other.negate()); |
| 405 | } else { | |
| 406 | 0 | return this.negate().multiply(other).negate(); |
| 407 | } | |
| 408 | 0 | } else if (other.isNegative()) { |
| 409 | 0 | return this.multiply(other.negate()).negate(); |
| 410 | } | |
| 411 | ||
| 412 | // If both Timestamps are small, use float multiplication | |
| 413 | 0 | if (this.lessThan(Timestamp.TWO_PWR_24_) && |
| 414 | other.lessThan(Timestamp.TWO_PWR_24_)) { | |
| 415 | 0 | return Timestamp.fromNumber(this.toNumber() * other.toNumber()); |
| 416 | } | |
| 417 | ||
| 418 | // Divide each Timestamp into 4 chunks of 16 bits, and then add up 4x4 products. | |
| 419 | // We can skip products that would overflow. | |
| 420 | ||
| 421 | 0 | var a48 = this.high_ >>> 16; |
| 422 | 0 | var a32 = this.high_ & 0xFFFF; |
| 423 | 0 | var a16 = this.low_ >>> 16; |
| 424 | 0 | var a00 = this.low_ & 0xFFFF; |
| 425 | ||
| 426 | 0 | var b48 = other.high_ >>> 16; |
| 427 | 0 | var b32 = other.high_ & 0xFFFF; |
| 428 | 0 | var b16 = other.low_ >>> 16; |
| 429 | 0 | var b00 = other.low_ & 0xFFFF; |
| 430 | ||
| 431 | 0 | var c48 = 0, c32 = 0, c16 = 0, c00 = 0; |
| 432 | 0 | c00 += a00 * b00; |
| 433 | 0 | c16 += c00 >>> 16; |
| 434 | 0 | c00 &= 0xFFFF; |
| 435 | 0 | c16 += a16 * b00; |
| 436 | 0 | c32 += c16 >>> 16; |
| 437 | 0 | c16 &= 0xFFFF; |
| 438 | 0 | c16 += a00 * b16; |
| 439 | 0 | c32 += c16 >>> 16; |
| 440 | 0 | c16 &= 0xFFFF; |
| 441 | 0 | c32 += a32 * b00; |
| 442 | 0 | c48 += c32 >>> 16; |
| 443 | 0 | c32 &= 0xFFFF; |
| 444 | 0 | c32 += a16 * b16; |
| 445 | 0 | c48 += c32 >>> 16; |
| 446 | 0 | c32 &= 0xFFFF; |
| 447 | 0 | c32 += a00 * b32; |
| 448 | 0 | c48 += c32 >>> 16; |
| 449 | 0 | c32 &= 0xFFFF; |
| 450 | 0 | c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; |
| 451 | 0 | c48 &= 0xFFFF; |
| 452 | 0 | return Timestamp.fromBits((c16 << 16) | c00, (c48 << 16) | c32); |
| 453 | }; | |
| 454 | ||
| 455 | /** | |
| 456 | * Returns this Timestamp divided by the given one. | |
| 457 | * | |
| 458 | * @param {Timestamp} other Timestamp by which to divide. | |
| 459 | * @return {Timestamp} this Timestamp divided by the given one. | |
| 460 | * @api public | |
| 461 | */ | |
| 462 | 1 | Timestamp.prototype.div = function(other) { |
| 463 | 0 | if (other.isZero()) { |
| 464 | 0 | throw Error('division by zero'); |
| 465 | 0 | } else if (this.isZero()) { |
| 466 | 0 | return Timestamp.ZERO; |
| 467 | } | |
| 468 | ||
| 469 | 0 | if (this.equals(Timestamp.MIN_VALUE)) { |
| 470 | 0 | if (other.equals(Timestamp.ONE) || |
| 471 | other.equals(Timestamp.NEG_ONE)) { | |
| 472 | 0 | return Timestamp.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE |
| 473 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 474 | 0 | return Timestamp.ONE; |
| 475 | } else { | |
| 476 | // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. | |
| 477 | 0 | var halfThis = this.shiftRight(1); |
| 478 | 0 | var approx = halfThis.div(other).shiftLeft(1); |
| 479 | 0 | if (approx.equals(Timestamp.ZERO)) { |
| 480 | 0 | return other.isNegative() ? Timestamp.ONE : Timestamp.NEG_ONE; |
| 481 | } else { | |
| 482 | 0 | var rem = this.subtract(other.multiply(approx)); |
| 483 | 0 | var result = approx.add(rem.div(other)); |
| 484 | 0 | return result; |
| 485 | } | |
| 486 | } | |
| 487 | 0 | } else if (other.equals(Timestamp.MIN_VALUE)) { |
| 488 | 0 | return Timestamp.ZERO; |
| 489 | } | |
| 490 | ||
| 491 | 0 | if (this.isNegative()) { |
| 492 | 0 | if (other.isNegative()) { |
| 493 | 0 | return this.negate().div(other.negate()); |
| 494 | } else { | |
| 495 | 0 | return this.negate().div(other).negate(); |
| 496 | } | |
| 497 | 0 | } else if (other.isNegative()) { |
| 498 | 0 | return this.div(other.negate()).negate(); |
| 499 | } | |
| 500 | ||
| 501 | // Repeat the following until the remainder is less than other: find a | |
| 502 | // floating-point that approximates remainder / other *from below*, add this | |
| 503 | // into the result, and subtract it from the remainder. It is critical that | |
| 504 | // the approximate value is less than or equal to the real value so that the | |
| 505 | // remainder never becomes negative. | |
| 506 | 0 | var res = Timestamp.ZERO; |
| 507 | 0 | var rem = this; |
| 508 | 0 | while (rem.greaterThanOrEqual(other)) { |
| 509 | // Approximate the result of division. This may be a little greater or | |
| 510 | // smaller than the actual value. | |
| 511 | 0 | var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber())); |
| 512 | ||
| 513 | // We will tweak the approximate result by changing it in the 48-th digit or | |
| 514 | // the smallest non-fractional digit, whichever is larger. | |
| 515 | 0 | var log2 = Math.ceil(Math.log(approx) / Math.LN2); |
| 516 | 0 | var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48); |
| 517 | ||
| 518 | // Decrease the approximation until it is smaller than the remainder. Note | |
| 519 | // that if it is too large, the product overflows and is negative. | |
| 520 | 0 | var approxRes = Timestamp.fromNumber(approx); |
| 521 | 0 | var approxRem = approxRes.multiply(other); |
| 522 | 0 | while (approxRem.isNegative() || approxRem.greaterThan(rem)) { |
| 523 | 0 | approx -= delta; |
| 524 | 0 | approxRes = Timestamp.fromNumber(approx); |
| 525 | 0 | approxRem = approxRes.multiply(other); |
| 526 | } | |
| 527 | ||
| 528 | // We know the answer can't be zero... and actually, zero would cause | |
| 529 | // infinite recursion since we would make no progress. | |
| 530 | 0 | if (approxRes.isZero()) { |
| 531 | 0 | approxRes = Timestamp.ONE; |
| 532 | } | |
| 533 | ||
| 534 | 0 | res = res.add(approxRes); |
| 535 | 0 | rem = rem.subtract(approxRem); |
| 536 | } | |
| 537 | 0 | return res; |
| 538 | }; | |
| 539 | ||
| 540 | /** | |
| 541 | * Returns this Timestamp modulo the given one. | |
| 542 | * | |
| 543 | * @param {Timestamp} other Timestamp by which to mod. | |
| 544 | * @return {Timestamp} this Timestamp modulo the given one. | |
| 545 | * @api public | |
| 546 | */ | |
| 547 | 1 | Timestamp.prototype.modulo = function(other) { |
| 548 | 0 | return this.subtract(this.div(other).multiply(other)); |
| 549 | }; | |
| 550 | ||
| 551 | /** | |
| 552 | * The bitwise-NOT of this value. | |
| 553 | * | |
| 554 | * @return {Timestamp} the bitwise-NOT of this value. | |
| 555 | * @api public | |
| 556 | */ | |
| 557 | 1 | Timestamp.prototype.not = function() { |
| 558 | 0 | return Timestamp.fromBits(~this.low_, ~this.high_); |
| 559 | }; | |
| 560 | ||
| 561 | /** | |
| 562 | * Returns the bitwise-AND of this Timestamp and the given one. | |
| 563 | * | |
| 564 | * @param {Timestamp} other the Timestamp with which to AND. | |
| 565 | * @return {Timestamp} the bitwise-AND of this and the other. | |
| 566 | * @api public | |
| 567 | */ | |
| 568 | 1 | Timestamp.prototype.and = function(other) { |
| 569 | 0 | return Timestamp.fromBits(this.low_ & other.low_, this.high_ & other.high_); |
| 570 | }; | |
| 571 | ||
| 572 | /** | |
| 573 | * Returns the bitwise-OR of this Timestamp and the given one. | |
| 574 | * | |
| 575 | * @param {Timestamp} other the Timestamp with which to OR. | |
| 576 | * @return {Timestamp} the bitwise-OR of this and the other. | |
| 577 | * @api public | |
| 578 | */ | |
| 579 | 1 | Timestamp.prototype.or = function(other) { |
| 580 | 0 | return Timestamp.fromBits(this.low_ | other.low_, this.high_ | other.high_); |
| 581 | }; | |
| 582 | ||
| 583 | /** | |
| 584 | * Returns the bitwise-XOR of this Timestamp and the given one. | |
| 585 | * | |
| 586 | * @param {Timestamp} other the Timestamp with which to XOR. | |
| 587 | * @return {Timestamp} the bitwise-XOR of this and the other. | |
| 588 | * @api public | |
| 589 | */ | |
| 590 | 1 | Timestamp.prototype.xor = function(other) { |
| 591 | 0 | return Timestamp.fromBits(this.low_ ^ other.low_, this.high_ ^ other.high_); |
| 592 | }; | |
| 593 | ||
| 594 | /** | |
| 595 | * Returns this Timestamp with bits shifted to the left by the given amount. | |
| 596 | * | |
| 597 | * @param {Number} numBits the number of bits by which to shift. | |
| 598 | * @return {Timestamp} this shifted to the left by the given amount. | |
| 599 | * @api public | |
| 600 | */ | |
| 601 | 1 | Timestamp.prototype.shiftLeft = function(numBits) { |
| 602 | 0 | numBits &= 63; |
| 603 | 0 | if (numBits == 0) { |
| 604 | 0 | return this; |
| 605 | } else { | |
| 606 | 0 | var low = this.low_; |
| 607 | 0 | if (numBits < 32) { |
| 608 | 0 | var high = this.high_; |
| 609 | 0 | return Timestamp.fromBits( |
| 610 | low << numBits, | |
| 611 | (high << numBits) | (low >>> (32 - numBits))); | |
| 612 | } else { | |
| 613 | 0 | return Timestamp.fromBits(0, low << (numBits - 32)); |
| 614 | } | |
| 615 | } | |
| 616 | }; | |
| 617 | ||
| 618 | /** | |
| 619 | * Returns this Timestamp with bits shifted to the right by the given amount. | |
| 620 | * | |
| 621 | * @param {Number} numBits the number of bits by which to shift. | |
| 622 | * @return {Timestamp} this shifted to the right by the given amount. | |
| 623 | * @api public | |
| 624 | */ | |
| 625 | 1 | Timestamp.prototype.shiftRight = function(numBits) { |
| 626 | 0 | numBits &= 63; |
| 627 | 0 | if (numBits == 0) { |
| 628 | 0 | return this; |
| 629 | } else { | |
| 630 | 0 | var high = this.high_; |
| 631 | 0 | if (numBits < 32) { |
| 632 | 0 | var low = this.low_; |
| 633 | 0 | return Timestamp.fromBits( |
| 634 | (low >>> numBits) | (high << (32 - numBits)), | |
| 635 | high >> numBits); | |
| 636 | } else { | |
| 637 | 0 | return Timestamp.fromBits( |
| 638 | high >> (numBits - 32), | |
| 639 | high >= 0 ? 0 : -1); | |
| 640 | } | |
| 641 | } | |
| 642 | }; | |
| 643 | ||
| 644 | /** | |
| 645 | * Returns this Timestamp with bits shifted to the right by the given amount, with the new top bits matching the current sign bit. | |
| 646 | * | |
| 647 | * @param {Number} numBits the number of bits by which to shift. | |
| 648 | * @return {Timestamp} this shifted to the right by the given amount, with zeros placed into the new leading bits. | |
| 649 | * @api public | |
| 650 | */ | |
| 651 | 1 | Timestamp.prototype.shiftRightUnsigned = function(numBits) { |
| 652 | 0 | numBits &= 63; |
| 653 | 0 | if (numBits == 0) { |
| 654 | 0 | return this; |
| 655 | } else { | |
| 656 | 0 | var high = this.high_; |
| 657 | 0 | if (numBits < 32) { |
| 658 | 0 | var low = this.low_; |
| 659 | 0 | return Timestamp.fromBits( |
| 660 | (low >>> numBits) | (high << (32 - numBits)), | |
| 661 | high >>> numBits); | |
| 662 | 0 | } else if (numBits == 32) { |
| 663 | 0 | return Timestamp.fromBits(high, 0); |
| 664 | } else { | |
| 665 | 0 | return Timestamp.fromBits(high >>> (numBits - 32), 0); |
| 666 | } | |
| 667 | } | |
| 668 | }; | |
| 669 | ||
| 670 | /** | |
| 671 | * Returns a Timestamp representing the given (32-bit) integer value. | |
| 672 | * | |
| 673 | * @param {Number} value the 32-bit integer in question. | |
| 674 | * @return {Timestamp} the corresponding Timestamp value. | |
| 675 | * @api public | |
| 676 | */ | |
| 677 | 1 | Timestamp.fromInt = function(value) { |
| 678 | 4 | if (-128 <= value && value < 128) { |
| 679 | 3 | var cachedObj = Timestamp.INT_CACHE_[value]; |
| 680 | 3 | if (cachedObj) { |
| 681 | 0 | return cachedObj; |
| 682 | } | |
| 683 | } | |
| 684 | ||
| 685 | 4 | var obj = new Timestamp(value | 0, value < 0 ? -1 : 0); |
| 686 | 4 | if (-128 <= value && value < 128) { |
| 687 | 3 | Timestamp.INT_CACHE_[value] = obj; |
| 688 | } | |
| 689 | 4 | return obj; |
| 690 | }; | |
| 691 | ||
| 692 | /** | |
| 693 | * Returns a Timestamp representing the given value, provided that it is a finite number. Otherwise, zero is returned. | |
| 694 | * | |
| 695 | * @param {Number} value the number in question. | |
| 696 | * @return {Timestamp} the corresponding Timestamp value. | |
| 697 | * @api public | |
| 698 | */ | |
| 699 | 1 | Timestamp.fromNumber = function(value) { |
| 700 | 0 | if (isNaN(value) || !isFinite(value)) { |
| 701 | 0 | return Timestamp.ZERO; |
| 702 | 0 | } else if (value <= -Timestamp.TWO_PWR_63_DBL_) { |
| 703 | 0 | return Timestamp.MIN_VALUE; |
| 704 | 0 | } else if (value + 1 >= Timestamp.TWO_PWR_63_DBL_) { |
| 705 | 0 | return Timestamp.MAX_VALUE; |
| 706 | 0 | } else if (value < 0) { |
| 707 | 0 | return Timestamp.fromNumber(-value).negate(); |
| 708 | } else { | |
| 709 | 0 | return new Timestamp( |
| 710 | (value % Timestamp.TWO_PWR_32_DBL_) | 0, | |
| 711 | (value / Timestamp.TWO_PWR_32_DBL_) | 0); | |
| 712 | } | |
| 713 | }; | |
| 714 | ||
| 715 | /** | |
| 716 | * Returns a Timestamp representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits. | |
| 717 | * | |
| 718 | * @param {Number} lowBits the low 32-bits. | |
| 719 | * @param {Number} highBits the high 32-bits. | |
| 720 | * @return {Timestamp} the corresponding Timestamp value. | |
| 721 | * @api public | |
| 722 | */ | |
| 723 | 1 | Timestamp.fromBits = function(lowBits, highBits) { |
| 724 | 2 | return new Timestamp(lowBits, highBits); |
| 725 | }; | |
| 726 | ||
| 727 | /** | |
| 728 | * Returns a Timestamp representation of the given string, written using the given radix. | |
| 729 | * | |
| 730 | * @param {String} str the textual representation of the Timestamp. | |
| 731 | * @param {Number} opt_radix the radix in which the text is written. | |
| 732 | * @return {Timestamp} the corresponding Timestamp value. | |
| 733 | * @api public | |
| 734 | */ | |
| 735 | 1 | Timestamp.fromString = function(str, opt_radix) { |
| 736 | 0 | if (str.length == 0) { |
| 737 | 0 | throw Error('number format error: empty string'); |
| 738 | } | |
| 739 | ||
| 740 | 0 | var radix = opt_radix || 10; |
| 741 | 0 | if (radix < 2 || 36 < radix) { |
| 742 | 0 | throw Error('radix out of range: ' + radix); |
| 743 | } | |
| 744 | ||
| 745 | 0 | if (str.charAt(0) == '-') { |
| 746 | 0 | return Timestamp.fromString(str.substring(1), radix).negate(); |
| 747 | 0 | } else if (str.indexOf('-') >= 0) { |
| 748 | 0 | throw Error('number format error: interior "-" character: ' + str); |
| 749 | } | |
| 750 | ||
| 751 | // Do several (8) digits each time through the loop, so as to | |
| 752 | // minimize the calls to the very expensive emulated div. | |
| 753 | 0 | var radixToPower = Timestamp.fromNumber(Math.pow(radix, 8)); |
| 754 | ||
| 755 | 0 | var result = Timestamp.ZERO; |
| 756 | 0 | for (var i = 0; i < str.length; i += 8) { |
| 757 | 0 | var size = Math.min(8, str.length - i); |
| 758 | 0 | var value = parseInt(str.substring(i, i + size), radix); |
| 759 | 0 | if (size < 8) { |
| 760 | 0 | var power = Timestamp.fromNumber(Math.pow(radix, size)); |
| 761 | 0 | result = result.multiply(power).add(Timestamp.fromNumber(value)); |
| 762 | } else { | |
| 763 | 0 | result = result.multiply(radixToPower); |
| 764 | 0 | result = result.add(Timestamp.fromNumber(value)); |
| 765 | } | |
| 766 | } | |
| 767 | 0 | return result; |
| 768 | }; | |
| 769 | ||
| 770 | // NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the | |
| 771 | // from* methods on which they depend. | |
| 772 | ||
| 773 | ||
| 774 | /** | |
| 775 | * A cache of the Timestamp representations of small integer values. | |
| 776 | * @type {Object} | |
| 777 | * @api private | |
| 778 | */ | |
| 779 | 1 | Timestamp.INT_CACHE_ = {}; |
| 780 | ||
| 781 | // NOTE: the compiler should inline these constant values below and then remove | |
| 782 | // these variables, so there should be no runtime penalty for these. | |
| 783 | ||
| 784 | /** | |
| 785 | * Number used repeated below in calculations. This must appear before the | |
| 786 | * first call to any from* function below. | |
| 787 | * @type {number} | |
| 788 | * @api private | |
| 789 | */ | |
| 790 | 1 | Timestamp.TWO_PWR_16_DBL_ = 1 << 16; |
| 791 | ||
| 792 | /** | |
| 793 | * @type {number} | |
| 794 | * @api private | |
| 795 | */ | |
| 796 | 1 | Timestamp.TWO_PWR_24_DBL_ = 1 << 24; |
| 797 | ||
| 798 | /** | |
| 799 | * @type {number} | |
| 800 | * @api private | |
| 801 | */ | |
| 802 | 1 | Timestamp.TWO_PWR_32_DBL_ = Timestamp.TWO_PWR_16_DBL_ * Timestamp.TWO_PWR_16_DBL_; |
| 803 | ||
| 804 | /** | |
| 805 | * @type {number} | |
| 806 | * @api private | |
| 807 | */ | |
| 808 | 1 | Timestamp.TWO_PWR_31_DBL_ = Timestamp.TWO_PWR_32_DBL_ / 2; |
| 809 | ||
| 810 | /** | |
| 811 | * @type {number} | |
| 812 | * @api private | |
| 813 | */ | |
| 814 | 1 | Timestamp.TWO_PWR_48_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_16_DBL_; |
| 815 | ||
| 816 | /** | |
| 817 | * @type {number} | |
| 818 | * @api private | |
| 819 | */ | |
| 820 | 1 | Timestamp.TWO_PWR_64_DBL_ = Timestamp.TWO_PWR_32_DBL_ * Timestamp.TWO_PWR_32_DBL_; |
| 821 | ||
| 822 | /** | |
| 823 | * @type {number} | |
| 824 | * @api private | |
| 825 | */ | |
| 826 | 1 | Timestamp.TWO_PWR_63_DBL_ = Timestamp.TWO_PWR_64_DBL_ / 2; |
| 827 | ||
| 828 | /** @type {Timestamp} */ | |
| 829 | 1 | Timestamp.ZERO = Timestamp.fromInt(0); |
| 830 | ||
| 831 | /** @type {Timestamp} */ | |
| 832 | 1 | Timestamp.ONE = Timestamp.fromInt(1); |
| 833 | ||
| 834 | /** @type {Timestamp} */ | |
| 835 | 1 | Timestamp.NEG_ONE = Timestamp.fromInt(-1); |
| 836 | ||
| 837 | /** @type {Timestamp} */ | |
| 838 | 1 | Timestamp.MAX_VALUE = |
| 839 | Timestamp.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0); | |
| 840 | ||
| 841 | /** @type {Timestamp} */ | |
| 842 | 1 | Timestamp.MIN_VALUE = Timestamp.fromBits(0, 0x80000000 | 0); |
| 843 | ||
| 844 | /** | |
| 845 | * @type {Timestamp} | |
| 846 | * @api private | |
| 847 | */ | |
| 848 | 1 | Timestamp.TWO_PWR_24_ = Timestamp.fromInt(1 << 24); |
| 849 | ||
| 850 | /** | |
| 851 | * Expose. | |
| 852 | */ | |
| 853 | 1 | exports.Timestamp = Timestamp; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var format = require('util').format; |
| 2 | ||
| 3 | 1 | var MongoAuthProcess = function(host, port, service_name) { |
| 4 | // Check what system we are on | |
| 5 | 0 | if(process.platform == 'win32') { |
| 6 | 0 | this._processor = new Win32MongoProcessor(host, port, service_name); |
| 7 | } else { | |
| 8 | 0 | this._processor = new UnixMongoProcessor(host, port, service_name); |
| 9 | } | |
| 10 | } | |
| 11 | ||
| 12 | 1 | MongoAuthProcess.prototype.init = function(username, password, callback) { |
| 13 | 0 | this._processor.init(username, password, callback); |
| 14 | } | |
| 15 | ||
| 16 | 1 | MongoAuthProcess.prototype.transition = function(payload, callback) { |
| 17 | 0 | this._processor.transition(payload, callback); |
| 18 | } | |
| 19 | ||
| 20 | /******************************************************************* | |
| 21 | * | |
| 22 | * Win32 SSIP Processor for MongoDB | |
| 23 | * | |
| 24 | *******************************************************************/ | |
| 25 | 1 | var Win32MongoProcessor = function(host, port, service_name) { |
| 26 | 0 | this.host = host; |
| 27 | 0 | this.port = port |
| 28 | // SSIP classes | |
| 29 | 0 | this.ssip = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/node_modules/kerberos/lib/auth_processes/../kerberos").SSIP; |
| 30 | // Set up first transition | |
| 31 | 0 | this._transition = Win32MongoProcessor.first_transition(this); |
| 32 | // Set up service name | |
| 33 | 0 | service_name = service_name || "mongodb"; |
| 34 | // Set up target | |
| 35 | 0 | this.target = format("%s/%s", service_name, host); |
| 36 | // Number of retries | |
| 37 | 0 | this.retries = 10; |
| 38 | } | |
| 39 | ||
| 40 | 1 | Win32MongoProcessor.prototype.init = function(username, password, callback) { |
| 41 | 0 | var self = this; |
| 42 | // Save the values used later | |
| 43 | 0 | this.username = username; |
| 44 | 0 | this.password = password; |
| 45 | // Aquire credentials | |
| 46 | 0 | this.ssip.SecurityCredentials.aquire_kerberos(username, password, function(err, security_credentials) { |
| 47 | 0 | if(err) return callback(err); |
| 48 | // Save credentials | |
| 49 | 0 | self.security_credentials = security_credentials; |
| 50 | // Callback with success | |
| 51 | 0 | callback(null); |
| 52 | }); | |
| 53 | } | |
| 54 | ||
| 55 | 1 | Win32MongoProcessor.prototype.transition = function(payload, callback) { |
| 56 | 0 | if(this._transition == null) return callback(new Error("Transition finished")); |
| 57 | 0 | this._transition(payload, callback); |
| 58 | } | |
| 59 | ||
| 60 | 1 | Win32MongoProcessor.first_transition = function(self) { |
| 61 | 0 | return function(payload, callback) { |
| 62 | 0 | self.ssip.SecurityContext.initialize( |
| 63 | self.security_credentials, | |
| 64 | self.target, | |
| 65 | payload, function(err, security_context) { | |
| 66 | 0 | if(err) return callback(err); |
| 67 | ||
| 68 | // If no context try again until we have no more retries | |
| 69 | 0 | if(!security_context.hasContext) { |
| 70 | 0 | if(self.retries == 0) return callback(new Error("Failed to initialize security context")); |
| 71 | // Update the number of retries | |
| 72 | 0 | self.retries = self.retries - 1; |
| 73 | // Set next transition | |
| 74 | 0 | return self.transition(payload, callback); |
| 75 | } | |
| 76 | ||
| 77 | // Set next transition | |
| 78 | 0 | self._transition = Win32MongoProcessor.second_transition(self); |
| 79 | 0 | self.security_context = security_context; |
| 80 | // Return the payload | |
| 81 | 0 | callback(null, security_context.payload); |
| 82 | }); | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | 1 | Win32MongoProcessor.second_transition = function(self) { |
| 87 | 0 | return function(payload, callback) { |
| 88 | // Perform a step | |
| 89 | 0 | self.security_context.initialize(self.target, payload, function(err, security_context) { |
| 90 | 0 | if(err) return callback(err); |
| 91 | ||
| 92 | // If no context try again until we have no more retries | |
| 93 | 0 | if(!security_context.hasContext) { |
| 94 | 0 | if(self.retries == 0) return callback(new Error("Failed to initialize security context")); |
| 95 | // Update the number of retries | |
| 96 | 0 | self.retries = self.retries - 1; |
| 97 | // Set next transition | |
| 98 | 0 | self._transition = Win32MongoProcessor.first_transition(self); |
| 99 | // Retry | |
| 100 | 0 | return self.transition(payload, callback); |
| 101 | } | |
| 102 | ||
| 103 | // Set next transition | |
| 104 | 0 | self._transition = Win32MongoProcessor.third_transition(self); |
| 105 | // Return the payload | |
| 106 | 0 | callback(null, security_context.payload); |
| 107 | }); | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | 1 | Win32MongoProcessor.third_transition = function(self) { |
| 112 | 0 | return function(payload, callback) { |
| 113 | 0 | var messageLength = 0; |
| 114 | // Get the raw bytes | |
| 115 | 0 | var encryptedBytes = new Buffer(payload, 'base64'); |
| 116 | 0 | var encryptedMessage = new Buffer(messageLength); |
| 117 | // Copy first byte | |
| 118 | 0 | encryptedBytes.copy(encryptedMessage, 0, 0, messageLength); |
| 119 | // Set up trailer | |
| 120 | 0 | var securityTrailerLength = encryptedBytes.length - messageLength; |
| 121 | 0 | var securityTrailer = new Buffer(securityTrailerLength); |
| 122 | // Copy the bytes | |
| 123 | 0 | encryptedBytes.copy(securityTrailer, 0, messageLength, securityTrailerLength); |
| 124 | ||
| 125 | // Types used | |
| 126 | 0 | var SecurityBuffer = self.ssip.SecurityBuffer; |
| 127 | 0 | var SecurityBufferDescriptor = self.ssip.SecurityBufferDescriptor; |
| 128 | ||
| 129 | // Set up security buffers | |
| 130 | 0 | var buffers = [ |
| 131 | new SecurityBuffer(SecurityBuffer.DATA, encryptedBytes) | |
| 132 | , new SecurityBuffer(SecurityBuffer.STREAM, securityTrailer) | |
| 133 | ]; | |
| 134 | ||
| 135 | // Set up the descriptor | |
| 136 | 0 | var descriptor = new SecurityBufferDescriptor(buffers); |
| 137 | ||
| 138 | // Decrypt the data | |
| 139 | 0 | self.security_context.decryptMessage(descriptor, function(err, security_context) { |
| 140 | 0 | if(err) return callback(err); |
| 141 | ||
| 142 | 0 | var length = 4; |
| 143 | 0 | if(self.username != null) { |
| 144 | 0 | length += self.username.length; |
| 145 | } | |
| 146 | ||
| 147 | 0 | var bytesReceivedFromServer = new Buffer(length); |
| 148 | 0 | bytesReceivedFromServer[0] = 0x01; // NO_PROTECTION |
| 149 | 0 | bytesReceivedFromServer[1] = 0x00; // NO_PROTECTION |
| 150 | 0 | bytesReceivedFromServer[2] = 0x00; // NO_PROTECTION |
| 151 | 0 | bytesReceivedFromServer[3] = 0x00; // NO_PROTECTION |
| 152 | ||
| 153 | 0 | if(self.username != null) { |
| 154 | 0 | var authorization_id_bytes = new Buffer(self.username, 'utf8'); |
| 155 | 0 | authorization_id_bytes.copy(bytesReceivedFromServer, 4, 0); |
| 156 | } | |
| 157 | ||
| 158 | 0 | self.security_context.queryContextAttributes(0x00, function(err, sizes) { |
| 159 | 0 | if(err) return callback(err); |
| 160 | ||
| 161 | 0 | var buffers = [ |
| 162 | new SecurityBuffer(SecurityBuffer.TOKEN, new Buffer(sizes.securityTrailer)) | |
| 163 | , new SecurityBuffer(SecurityBuffer.DATA, bytesReceivedFromServer) | |
| 164 | , new SecurityBuffer(SecurityBuffer.PADDING, new Buffer(sizes.blockSize)) | |
| 165 | ] | |
| 166 | ||
| 167 | 0 | var descriptor = new SecurityBufferDescriptor(buffers); |
| 168 | ||
| 169 | 0 | self.security_context.encryptMessage(descriptor, 0x80000001, function(err, security_context) { |
| 170 | 0 | if(err) return callback(err); |
| 171 | 0 | callback(null, security_context.payload); |
| 172 | }); | |
| 173 | }); | |
| 174 | }); | |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | /******************************************************************* | |
| 179 | * | |
| 180 | * UNIX MIT Kerberos processor | |
| 181 | * | |
| 182 | *******************************************************************/ | |
| 183 | 1 | var UnixMongoProcessor = function(host, port, service_name) { |
| 184 | 0 | this.host = host; |
| 185 | 0 | this.port = port |
| 186 | // SSIP classes | |
| 187 | 0 | this.Kerberos = require("/Users/lcalvy/Documents/Boulot/Sources/tp-workflow-production/node_modules/mongoose/node_modules/mongodb/node_modules/kerberos/lib/auth_processes/../kerberos").Kerberos; |
| 188 | 0 | this.kerberos = new this.Kerberos(); |
| 189 | 0 | service_name = service_name || "mongodb"; |
| 190 | // Set up first transition | |
| 191 | 0 | this._transition = UnixMongoProcessor.first_transition(this); |
| 192 | // Set up target | |
| 193 | 0 | this.target = format("%s@%s", service_name, host); |
| 194 | // Number of retries | |
| 195 | 0 | this.retries = 10; |
| 196 | } | |
| 197 | ||
| 198 | 1 | UnixMongoProcessor.prototype.init = function(username, password, callback) { |
| 199 | 0 | var self = this; |
| 200 | 0 | this.username = username; |
| 201 | 0 | this.password = password; |
| 202 | // Call client initiate | |
| 203 | 0 | this.kerberos.authGSSClientInit( |
| 204 | self.target | |
| 205 | , this.Kerberos.GSS_C_MUTUAL_FLAG, function(err, context) { | |
| 206 | 0 | self.context = context; |
| 207 | // Return the context | |
| 208 | 0 | callback(null, context); |
| 209 | }); | |
| 210 | } | |
| 211 | ||
| 212 | 1 | UnixMongoProcessor.prototype.transition = function(payload, callback) { |
| 213 | 0 | if(this._transition == null) return callback(new Error("Transition finished")); |
| 214 | 0 | this._transition(payload, callback); |
| 215 | } | |
| 216 | ||
| 217 | 1 | UnixMongoProcessor.first_transition = function(self) { |
| 218 | 0 | return function(payload, callback) { |
| 219 | 0 | self.kerberos.authGSSClientStep(self.context, '', function(err, result) { |
| 220 | 0 | if(err) return callback(err); |
| 221 | // Set up the next step | |
| 222 | 0 | self._transition = UnixMongoProcessor.second_transition(self); |
| 223 | // Return the payload | |
| 224 | 0 | callback(null, self.context.response); |
| 225 | }) | |
| 226 | } | |
| 227 | } | |
| 228 | ||
| 229 | 1 | UnixMongoProcessor.second_transition = function(self) { |
| 230 | 0 | return function(payload, callback) { |
| 231 | 0 | self.kerberos.authGSSClientStep(self.context, payload, function(err, result) { |
| 232 | 0 | if(err && self.retries == 0) return callback(err); |
| 233 | // Attempt to re-establish a context | |
| 234 | 0 | if(err) { |
| 235 | // Adjust the number of retries | |
| 236 | 0 | self.retries = self.retries - 1; |
| 237 | // Call same step again | |
| 238 | 0 | return self.transition(payload, callback); |
| 239 | } | |
| 240 | ||
| 241 | // Set up the next step | |
| 242 | 0 | self._transition = UnixMongoProcessor.third_transition(self); |
| 243 | // Return the payload | |
| 244 | 0 | callback(null, self.context.response || ''); |
| 245 | }); | |
| 246 | } | |
| 247 | } | |
| 248 | ||
| 249 | 1 | UnixMongoProcessor.third_transition = function(self) { |
| 250 | 0 | return function(payload, callback) { |
| 251 | // GSS Client Unwrap | |
| 252 | 0 | self.kerberos.authGSSClientUnwrap(self.context, payload, function(err, result) { |
| 253 | 0 | if(err) return callback(err, false); |
| 254 | ||
| 255 | // Wrap the response | |
| 256 | 0 | self.kerberos.authGSSClientWrap(self.context, self.context.response, self.username, function(err, result) { |
| 257 | 0 | if(err) return callback(err, false); |
| 258 | // Set up the next step | |
| 259 | 0 | self._transition = UnixMongoProcessor.fourth_transition(self); |
| 260 | // Return the payload | |
| 261 | 0 | callback(null, self.context.response); |
| 262 | }); | |
| 263 | }); | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | 1 | UnixMongoProcessor.fourth_transition = function(self) { |
| 268 | 0 | return function(payload, callback) { |
| 269 | // Clean up context | |
| 270 | 0 | self.kerberos.authGSSClientClean(self.context, function(err, result) { |
| 271 | 0 | if(err) return callback(err, false); |
| 272 | // Set the transition to null | |
| 273 | 0 | self._transition = null; |
| 274 | // Callback with valid authentication | |
| 275 | 0 | callback(null, true); |
| 276 | }); | |
| 277 | } | |
| 278 | } | |
| 279 | ||
| 280 | // Set the process | |
| 281 | 1 | exports.MongoAuthProcess = MongoAuthProcess; |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var kerberos = require('../build/Release/kerberos') |
| 2 | , KerberosNative = kerberos.Kerberos; | |
| 3 | ||
| 4 | 1 | var Kerberos = function() { |
| 5 | 0 | this._native_kerberos = new KerberosNative(); |
| 6 | } | |
| 7 | ||
| 8 | 1 | Kerberos.prototype.authGSSClientInit = function(uri, flags, callback) { |
| 9 | 0 | return this._native_kerberos.authGSSClientInit(uri, flags, callback); |
| 10 | } | |
| 11 | ||
| 12 | 1 | Kerberos.prototype.authGSSClientStep = function(context, challenge, callback) { |
| 13 | 0 | if(typeof challenge == 'function') { |
| 14 | 0 | callback = challenge; |
| 15 | 0 | challenge = ''; |
| 16 | } | |
| 17 | ||
| 18 | 0 | return this._native_kerberos.authGSSClientStep(context, challenge, callback); |
| 19 | } | |
| 20 | ||
| 21 | 1 | Kerberos.prototype.authGSSClientUnwrap = function(context, challenge, callback) { |
| 22 | 0 | if(typeof challenge == 'function') { |
| 23 | 0 | callback = challenge; |
| 24 | 0 | challenge = ''; |
| 25 | } | |
| 26 | ||
| 27 | 0 | return this._native_kerberos.authGSSClientUnwrap(context, challenge, callback); |
| 28 | } | |
| 29 | ||
| 30 | 1 | Kerberos.prototype.authGSSClientWrap = function(context, challenge, user_name, callback) { |
| 31 | 0 | if(typeof user_name == 'function') { |
| 32 | 0 | callback = user_name; |
| 33 | 0 | user_name = ''; |
| 34 | } | |
| 35 | ||
| 36 | 0 | return this._native_kerberos.authGSSClientWrap(context, challenge, user_name, callback); |
| 37 | } | |
| 38 | ||
| 39 | 1 | Kerberos.prototype.authGSSClientClean = function(context, callback) { |
| 40 | 0 | return this._native_kerberos.authGSSClientClean(context, callback); |
| 41 | } | |
| 42 | ||
| 43 | 1 | Kerberos.prototype.acquireAlternateCredentials = function(user_name, password, domain) { |
| 44 | 0 | return this._native_kerberos.acquireAlternateCredentials(user_name, password, domain); |
| 45 | } | |
| 46 | ||
| 47 | 1 | Kerberos.prototype.prepareOutboundPackage = function(principal, inputdata) { |
| 48 | 0 | return this._native_kerberos.prepareOutboundPackage(principal, inputdata); |
| 49 | } | |
| 50 | ||
| 51 | 1 | Kerberos.prototype.decryptMessage = function(challenge) { |
| 52 | 0 | return this._native_kerberos.decryptMessage(challenge); |
| 53 | } | |
| 54 | ||
| 55 | 1 | Kerberos.prototype.encryptMessage = function(challenge) { |
| 56 | 0 | return this._native_kerberos.encryptMessage(challenge); |
| 57 | } | |
| 58 | ||
| 59 | 1 | Kerberos.prototype.queryContextAttribute = function(attribute) { |
| 60 | 0 | if(typeof attribute != 'number' && attribute != 0x00) throw new Error("Attribute not supported"); |
| 61 | 0 | return this._native_kerberos.queryContextAttribute(attribute); |
| 62 | } | |
| 63 | ||
| 64 | // Some useful result codes | |
| 65 | 1 | Kerberos.AUTH_GSS_CONTINUE = 0; |
| 66 | 1 | Kerberos.AUTH_GSS_COMPLETE = 1; |
| 67 | ||
| 68 | // Some useful gss flags | |
| 69 | 1 | Kerberos.GSS_C_DELEG_FLAG = 1; |
| 70 | 1 | Kerberos.GSS_C_MUTUAL_FLAG = 2; |
| 71 | 1 | Kerberos.GSS_C_REPLAY_FLAG = 4; |
| 72 | 1 | Kerberos.GSS_C_SEQUENCE_FLAG = 8; |
| 73 | 1 | Kerberos.GSS_C_CONF_FLAG = 16; |
| 74 | 1 | Kerberos.GSS_C_INTEG_FLAG = 32; |
| 75 | 1 | Kerberos.GSS_C_ANON_FLAG = 64; |
| 76 | 1 | Kerberos.GSS_C_PROT_READY_FLAG = 128; |
| 77 | 1 | Kerberos.GSS_C_TRANS_FLAG = 256; |
| 78 | ||
| 79 | // Export Kerberos class | |
| 80 | 1 | exports.Kerberos = Kerberos; |
| 81 | ||
| 82 | // If we have SSPI (windows) | |
| 83 | 1 | if(kerberos.SecurityCredentials) { |
| 84 | // Put all SSPI classes in it's own namespace | |
| 85 | 0 | exports.SSIP = { |
| 86 | SecurityCredentials: require('./win32/wrappers/security_credentials').SecurityCredentials | |
| 87 | , SecurityContext: require('./win32/wrappers/security_context').SecurityContext | |
| 88 | , SecurityBuffer: require('./win32/wrappers/security_buffer').SecurityBuffer | |
| 89 | , SecurityBufferDescriptor: require('./win32/wrappers/security_buffer_descriptor').SecurityBufferDescriptor | |
| 90 | } | |
| 91 | } | |
| 92 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Returns the value of object `o` at the given `path`. | |
| 4 | * | |
| 5 | * ####Example: | |
| 6 | * | |
| 7 | * var obj = { | |
| 8 | * comments: [ | |
| 9 | * { title: 'exciting!', _doc: { title: 'great!' }} | |
| 10 | * , { title: 'number dos' } | |
| 11 | * ] | |
| 12 | * } | |
| 13 | * | |
| 14 | * mpath.get('comments.0.title', o) // 'exciting!' | |
| 15 | * mpath.get('comments.0.title', o, '_doc') // 'great!' | |
| 16 | * mpath.get('comments.title', o) // ['exciting!', 'number dos'] | |
| 17 | * | |
| 18 | * // summary | |
| 19 | * mpath.get(path, o) | |
| 20 | * mpath.get(path, o, special) | |
| 21 | * mpath.get(path, o, map) | |
| 22 | * mpath.get(path, o, special, map) | |
| 23 | * | |
| 24 | * @param {String} path | |
| 25 | * @param {Object} o | |
| 26 | * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property. | |
| 27 | * @param {Function} [map] Optional function which receives each individual found value. The value returned from `map` is used in the original values place. | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | exports.get = function (path, o, special, map) { |
| 31 | 0 | if ('function' == typeof special) { |
| 32 | 0 | map = special; |
| 33 | 0 | special = undefined; |
| 34 | } | |
| 35 | ||
| 36 | 0 | map || (map = K); |
| 37 | ||
| 38 | 0 | var parts = 'string' == typeof path |
| 39 | ? path.split('.') | |
| 40 | : path | |
| 41 | ||
| 42 | 0 | if (!Array.isArray(parts)) { |
| 43 | 0 | throw new TypeError('Invalid `path`. Must be either string or array'); |
| 44 | } | |
| 45 | ||
| 46 | 0 | var obj = o |
| 47 | , part; | |
| 48 | ||
| 49 | 0 | for (var i = 0; i < parts.length; ++i) { |
| 50 | 0 | part = parts[i]; |
| 51 | ||
| 52 | 0 | if (Array.isArray(obj) && !/^\d+$/.test(part)) { |
| 53 | // reading a property from the array items | |
| 54 | 0 | var paths = parts.slice(i); |
| 55 | ||
| 56 | 0 | return obj.map(function (item) { |
| 57 | 0 | return item |
| 58 | ? exports.get(paths, item, special, map) | |
| 59 | : map(undefined); | |
| 60 | }); | |
| 61 | } | |
| 62 | ||
| 63 | 0 | obj = special && obj[special] |
| 64 | ? obj[special][part] | |
| 65 | : obj[part]; | |
| 66 | ||
| 67 | 0 | if (!obj) return map(obj); |
| 68 | } | |
| 69 | ||
| 70 | 0 | return map(obj); |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Sets the `val` at the given `path` of object `o`. | |
| 75 | * | |
| 76 | * @param {String} path | |
| 77 | * @param {Anything} val | |
| 78 | * @param {Object} o | |
| 79 | * @param {String} [special] When this property name is present on any object in the path, walking will continue on the value of this property. | |
| 80 | * @param {Function} [map] Optional function which is passed each individual value before setting it. The value returned from `map` is used in the original values place. | |
| 81 | ||
| 82 | */ | |
| 83 | ||
| 84 | 1 | exports.set = function (path, val, o, special, map, _copying) { |
| 85 | 0 | if ('function' == typeof special) { |
| 86 | 0 | map = special; |
| 87 | 0 | special = undefined; |
| 88 | } | |
| 89 | ||
| 90 | 0 | map || (map = K); |
| 91 | ||
| 92 | 0 | var parts = 'string' == typeof path |
| 93 | ? path.split('.') | |
| 94 | : path | |
| 95 | ||
| 96 | 0 | if (!Array.isArray(parts)) { |
| 97 | 0 | throw new TypeError('Invalid `path`. Must be either string or array'); |
| 98 | } | |
| 99 | ||
| 100 | 0 | if (null == o) return; |
| 101 | ||
| 102 | // the existance of $ in a path tells us if the user desires | |
| 103 | // the copying of an array instead of setting each value of | |
| 104 | // the array to the one by one to matching positions of the | |
| 105 | // current array. | |
| 106 | 0 | var copy = _copying || /\$/.test(path) |
| 107 | , obj = o | |
| 108 | , part | |
| 109 | ||
| 110 | 0 | for (var i = 0, len = parts.length - 1; i < len; ++i) { |
| 111 | 0 | part = parts[i]; |
| 112 | ||
| 113 | 0 | if ('$' == part) { |
| 114 | 0 | if (i == len - 1) { |
| 115 | 0 | break; |
| 116 | } else { | |
| 117 | 0 | continue; |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | 0 | if (Array.isArray(obj) && !/^\d+$/.test(part)) { |
| 122 | 0 | var paths = parts.slice(i); |
| 123 | 0 | if (!copy && Array.isArray(val)) { |
| 124 | 0 | for (var j = 0; j < obj.length && j < val.length; ++j) { |
| 125 | // assignment of single values of array | |
| 126 | 0 | exports.set(paths, val[j], obj[j], special, map, copy); |
| 127 | } | |
| 128 | } else { | |
| 129 | 0 | for (var j = 0; j < obj.length; ++j) { |
| 130 | // assignment of entire value | |
| 131 | 0 | exports.set(paths, val, obj[j], special, map, copy); |
| 132 | } | |
| 133 | } | |
| 134 | 0 | return; |
| 135 | } | |
| 136 | ||
| 137 | 0 | obj = special && obj[special] |
| 138 | ? obj[special][part] | |
| 139 | : obj[part]; | |
| 140 | ||
| 141 | 0 | if (!obj) return; |
| 142 | } | |
| 143 | ||
| 144 | // process the last property of the path | |
| 145 | ||
| 146 | 0 | part = parts[len]; |
| 147 | ||
| 148 | // use the special property if exists | |
| 149 | 0 | if (special && obj[special]) { |
| 150 | 0 | obj = obj[special]; |
| 151 | } | |
| 152 | ||
| 153 | // set the value on the last branch | |
| 154 | 0 | if (Array.isArray(obj) && !/^\d+$/.test(part)) { |
| 155 | 0 | if (!copy && Array.isArray(val)) { |
| 156 | 0 | for (var item, j = 0; j < obj.length && j < val.length; ++j) { |
| 157 | 0 | item = obj[j]; |
| 158 | 0 | if (item) { |
| 159 | 0 | if (item[special]) item = item[special]; |
| 160 | 0 | item[part] = map(val[j]); |
| 161 | } | |
| 162 | } | |
| 163 | } else { | |
| 164 | 0 | for (var j = 0; j < obj.length; ++j) { |
| 165 | 0 | item = obj[j]; |
| 166 | 0 | if (item) { |
| 167 | 0 | if (item[special]) item = item[special]; |
| 168 | 0 | item[part] = map(val); |
| 169 | } | |
| 170 | } | |
| 171 | } | |
| 172 | } else { | |
| 173 | 0 | obj[part] = map(val); |
| 174 | } | |
| 175 | } | |
| 176 | ||
| 177 | /*! | |
| 178 | * Returns the value passed to it. | |
| 179 | */ | |
| 180 | ||
| 181 | 1 | function K (v) { |
| 182 | 0 | return v; |
| 183 | } | |
| 184 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /*! | |
| 4 | * Module dependencies. | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var slice = function (arr, start, end) { |
| 8 | 0 | return Array.prototype.slice.call(arr, start, end) |
| 9 | }; | |
| 10 | 1 | var EventEmitter = require('events').EventEmitter; |
| 11 | ||
| 12 | /** | |
| 13 | * Promise constructor. | |
| 14 | * | |
| 15 | * _NOTE: The success and failure event names can be overridden by setting `Promise.SUCCESS` and `Promise.FAILURE` respectively._ | |
| 16 | * | |
| 17 | * @param {Function} back a function that accepts `fn(err, ...){}` as signature | |
| 18 | * @inherits NodeJS EventEmitter http://nodejs.org/api/events.html#events_class_events_eventemitter | |
| 19 | * @event `reject`: Emits when the promise is rejected (event name may be overridden) | |
| 20 | * @event `fulfill`: Emits when the promise is fulfilled (event name may be overridden) | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | function Promise(back) { |
| 25 | 0 | EventEmitter.call(this); |
| 26 | ||
| 27 | 0 | this.emitted = {}; |
| 28 | 0 | this.ended = false; |
| 29 | 0 | if ('function' == typeof back) |
| 30 | 0 | this.onResolve(back); |
| 31 | } | |
| 32 | ||
| 33 | /*! | |
| 34 | * event names | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | Promise.SUCCESS = 'fulfill'; |
| 38 | 1 | Promise.FAILURE = 'reject'; |
| 39 | ||
| 40 | /*! | |
| 41 | * Inherits from EventEmitter. | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | Promise.prototype.__proto__ = EventEmitter.prototype; |
| 45 | ||
| 46 | /** | |
| 47 | * Adds `listener` to the `event`. | |
| 48 | * | |
| 49 | * If `event` is either the success or failure event and the event has already been emitted, the`listener` is called immediately and passed the results of the original emitted event. | |
| 50 | * | |
| 51 | * @param {String} event | |
| 52 | * @param {Function} callback | |
| 53 | * @return {Promise} this | |
| 54 | * @api public | |
| 55 | */ | |
| 56 | ||
| 57 | 1 | Promise.prototype.on = function (event, callback) { |
| 58 | 0 | if (this.emitted[event]) |
| 59 | 0 | callback.apply(this, this.emitted[event]); |
| 60 | else | |
| 61 | 0 | EventEmitter.prototype.on.call(this, event, callback); |
| 62 | ||
| 63 | 0 | return this; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Keeps track of emitted events to run them on `on`. | |
| 68 | * | |
| 69 | * @api private | |
| 70 | */ | |
| 71 | ||
| 72 | 1 | Promise.prototype.emit = function (event) { |
| 73 | // ensures a promise can't be fulfill() or reject() more than once | |
| 74 | 0 | var success = this.constructor.SUCCESS; |
| 75 | 0 | var failure = this.constructor.FAILURE; |
| 76 | ||
| 77 | 0 | if (event == success || event == failure) { |
| 78 | 0 | if (this.emitted[success] || this.emitted[failure]) { |
| 79 | 0 | return this; |
| 80 | } | |
| 81 | 0 | this.emitted[event] = slice(arguments, 1); |
| 82 | } | |
| 83 | ||
| 84 | 0 | return EventEmitter.prototype.emit.apply(this, arguments); |
| 85 | } | |
| 86 | ||
| 87 | /** | |
| 88 | * Fulfills this promise with passed arguments. | |
| 89 | * | |
| 90 | * If this promise has already been fulfilled or rejected, no action is taken. | |
| 91 | * | |
| 92 | * @api public | |
| 93 | */ | |
| 94 | ||
| 95 | 1 | Promise.prototype.fulfill = function () { |
| 96 | 0 | var args = slice(arguments); |
| 97 | 0 | return this.emit.apply(this, [this.constructor.SUCCESS].concat(args)); |
| 98 | } | |
| 99 | ||
| 100 | /** | |
| 101 | * Rejects this promise with `reason`. | |
| 102 | * | |
| 103 | * If this promise has already been fulfilled or rejected, no action is taken. | |
| 104 | * | |
| 105 | * @api public | |
| 106 | * @param {Object|String} reason | |
| 107 | * @return {Promise} this | |
| 108 | */ | |
| 109 | ||
| 110 | 1 | Promise.prototype.reject = function (reason) { |
| 111 | 0 | return this.emit(this.constructor.FAILURE, reason); |
| 112 | } | |
| 113 | ||
| 114 | /** | |
| 115 | * Resolves this promise to a rejected state if `err` is passed or | |
| 116 | * fulfilled state if no `err` is passed. | |
| 117 | * | |
| 118 | * @param {Error} [err] error or null | |
| 119 | * @param {Object} [val] value to fulfill the promise with | |
| 120 | * @api public | |
| 121 | */ | |
| 122 | ||
| 123 | 1 | Promise.prototype.resolve = function (err, val) { |
| 124 | 0 | if (err) return this.reject(err); |
| 125 | 0 | return this.fulfill(val); |
| 126 | } | |
| 127 | ||
| 128 | /** | |
| 129 | * Adds a listener to the SUCCESS event. | |
| 130 | * | |
| 131 | * @return {Promise} this | |
| 132 | * @api public | |
| 133 | */ | |
| 134 | ||
| 135 | 1 | Promise.prototype.onFulfill = function (fn) { |
| 136 | 0 | if (!fn) return this; |
| 137 | 0 | if ('function' != typeof fn) throw new TypeError("fn should be a function"); |
| 138 | 0 | return this.on(this.constructor.SUCCESS, fn); |
| 139 | } | |
| 140 | ||
| 141 | 1 | Promise.prototype.hasRejectListeners = function () { |
| 142 | 0 | return this.listeners(this.constructor.FAILURE).length > 0; |
| 143 | }; | |
| 144 | /** | |
| 145 | * Adds a listener to the FAILURE event. | |
| 146 | * | |
| 147 | * @return {Promise} this | |
| 148 | * @api public | |
| 149 | */ | |
| 150 | ||
| 151 | 1 | Promise.prototype.onReject = function (fn) { |
| 152 | 0 | if (!fn) return this; |
| 153 | 0 | if ('function' != typeof fn) throw new TypeError("fn should be a function"); |
| 154 | 0 | return this.on(this.constructor.FAILURE, fn); |
| 155 | } | |
| 156 | ||
| 157 | /** | |
| 158 | * Adds a single function as a listener to both SUCCESS and FAILURE. | |
| 159 | * | |
| 160 | * It will be executed with traditional node.js argument position: | |
| 161 | * function (err, args...) {} | |
| 162 | * | |
| 163 | * @param {Function} fn | |
| 164 | * @return {Promise} this | |
| 165 | */ | |
| 166 | ||
| 167 | 1 | Promise.prototype.onResolve = function (fn) { |
| 168 | 0 | if (!fn) return this; |
| 169 | 0 | if ('function' != typeof fn) throw new TypeError("fn should be a function"); |
| 170 | ||
| 171 | 0 | this.on(this.constructor.FAILURE, function (err) { |
| 172 | 0 | fn.apply(this, [err]); |
| 173 | }); | |
| 174 | ||
| 175 | 0 | this.on(this.constructor.SUCCESS, function () { |
| 176 | 0 | var args = slice(arguments); |
| 177 | 0 | fn.apply(this, [null].concat(args)); |
| 178 | }); | |
| 179 | ||
| 180 | 0 | return this; |
| 181 | } | |
| 182 | ||
| 183 | /** | |
| 184 | * Creates a new promise and returns it. If `onFulfill` or | |
| 185 | * `onReject` are passed, they are added as SUCCESS/ERROR callbacks | |
| 186 | * to this promise after the next tick. | |
| 187 | * | |
| 188 | * Conforms to [promises/A+](https://github.com/promises-aplus/promises-spec) specification. Read for more detail how to use this method. | |
| 189 | * | |
| 190 | * ####Example: | |
| 191 | * | |
| 192 | * var p = new Promise; | |
| 193 | * p.then(function (arg) { | |
| 194 | * return arg + 1; | |
| 195 | * }).then(function (arg) { | |
| 196 | * throw new Error(arg + ' is an error!'); | |
| 197 | * }).then(null, function (err) { | |
| 198 | * assert.ok(err instanceof Error); | |
| 199 | * assert.equal('2 is an error', err.message); | |
| 200 | * }); | |
| 201 | * p.complete(1); | |
| 202 | * | |
| 203 | * @see promises-A+ https://github.com/promises-aplus/promises-spec | |
| 204 | * @param {Function} onFulFill | |
| 205 | * @param {Function} [onReject] | |
| 206 | * @return {Promise} newPromise | |
| 207 | */ | |
| 208 | ||
| 209 | 1 | Promise.prototype.then = function (onFulfill, onReject) { |
| 210 | 0 | var self = this |
| 211 | , retPromise = new Promise; | |
| 212 | ||
| 213 | 0 | if ('function' == typeof onReject) { |
| 214 | 0 | self.onReject(handler(retPromise, onReject)); |
| 215 | } else { | |
| 216 | 0 | self.onReject(retPromise.reject.bind(retPromise)); |
| 217 | } | |
| 218 | 0 | if ('function' == typeof onFulfill) { |
| 219 | 0 | self.onFulfill(handler(retPromise, onFulfill)); |
| 220 | } else { | |
| 221 | 0 | self.onFulfill(retPromise.fulfill.bind(retPromise)); |
| 222 | } | |
| 223 | ||
| 224 | 0 | return retPromise; |
| 225 | }; | |
| 226 | ||
| 227 | ||
| 228 | 1 | function handler(retPromise, fn) { |
| 229 | 0 | return function handler() { |
| 230 | 0 | var args = arguments; |
| 231 | 0 | process.nextTick( |
| 232 | function in_the_handler() { | |
| 233 | 0 | if (retPromise.domain && retPromise.domain !== process.domain) retPromise.domain.enter(); |
| 234 | 0 | var x; |
| 235 | ||
| 236 | 0 | try { |
| 237 | 0 | x = fn.apply(undefined, args); |
| 238 | } catch (err) { | |
| 239 | 0 | if (retPromise.ended && !retPromise.hasRejectListeners()) throw err; |
| 240 | 0 | return retPromise.reject(err); |
| 241 | } | |
| 242 | ||
| 243 | 0 | resolve(retPromise, x); |
| 244 | 0 | return; |
| 245 | } | |
| 246 | ); | |
| 247 | } | |
| 248 | } | |
| 249 | ||
| 250 | 1 | function resolve(promise, x) { |
| 251 | 0 | var then; |
| 252 | 0 | var type; |
| 253 | 0 | var done; |
| 254 | 0 | var reject_; |
| 255 | 0 | var resolve_; |
| 256 | ||
| 257 | 0 | type = typeof x; |
| 258 | 0 | if ('undefined' == type) { |
| 259 | 0 | return promise.fulfill(x); |
| 260 | } | |
| 261 | ||
| 262 | 0 | if (promise === x) { |
| 263 | 0 | return promise.reject(new TypeError("promise and x are the same")); |
| 264 | } | |
| 265 | ||
| 266 | 0 | if (null != x) { |
| 267 | ||
| 268 | 0 | if ('object' == type || 'function' == type) { |
| 269 | 0 | try { |
| 270 | 0 | then = x.then; |
| 271 | } catch (err) { | |
| 272 | 0 | if (promise.ended && !promise.hasRejectListeners()) throw err; |
| 273 | 0 | return promise.reject(err); |
| 274 | } | |
| 275 | ||
| 276 | 0 | if ('function' == typeof then) { |
| 277 | 0 | try { |
| 278 | 0 | resolve_ = function () {var args = slice(arguments); resolve.apply(this, [promise].concat(args));}; |
| 279 | 0 | reject_ = promise.reject.bind(promise); |
| 280 | 0 | done = false; |
| 281 | 0 | return then.call( |
| 282 | x | |
| 283 | , function fulfill() { | |
| 284 | 0 | if (done) return; |
| 285 | 0 | done = true; |
| 286 | 0 | return resolve_.apply(this, arguments); |
| 287 | } | |
| 288 | , function reject() { | |
| 289 | 0 | if (done) return; |
| 290 | 0 | done = true; |
| 291 | 0 | return reject_.apply(this, arguments); |
| 292 | }) | |
| 293 | } catch (err) { | |
| 294 | 0 | if (done) return; |
| 295 | 0 | done = true; |
| 296 | 0 | if (promise.ended) throw err; |
| 297 | 0 | return promise.reject(err); |
| 298 | } | |
| 299 | } | |
| 300 | } | |
| 301 | } | |
| 302 | ||
| 303 | 0 | promise.fulfill(x); |
| 304 | } | |
| 305 | ||
| 306 | /** | |
| 307 | * Signifies that this promise was the last in a chain of `then()s`: if a handler passed to the call to `then` which produced this promise throws, the exception will go uncaught. | |
| 308 | * | |
| 309 | * ####Example: | |
| 310 | * | |
| 311 | * var p = new Promise; | |
| 312 | * p.then(function(){ throw new Error('shucks') }); | |
| 313 | * setTimeout(function () { | |
| 314 | * p.fulfill(); | |
| 315 | * // error was caught and swallowed by the promise returned from | |
| 316 | * // p.then(). we either have to always register handlers on | |
| 317 | * // the returned promises or we can do the following... | |
| 318 | * }, 10); | |
| 319 | * | |
| 320 | * // this time we use .end() which prevents catching thrown errors | |
| 321 | * var p = new Promise; | |
| 322 | * var p2 = p.then(function(){ throw new Error('shucks') }).end(); // <-- | |
| 323 | * setTimeout(function () { | |
| 324 | * p.fulfill(); // throws "shucks" | |
| 325 | * }, 10); | |
| 326 | * | |
| 327 | * @api public | |
| 328 | * @param {Function} [onReject] | |
| 329 | * @return {Promise} this | |
| 330 | */ | |
| 331 | ||
| 332 | 1 | Promise.prototype.end = function (onReject) { |
| 333 | 0 | this.onReject(onReject); |
| 334 | 0 | this.ended = true; |
| 335 | 0 | return this; |
| 336 | }; | |
| 337 | ||
| 338 | 1 | module.exports = Promise; |
| 339 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /** | |
| 4 | * methods a collection must implement | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var names = 'find findOne update remove count distict findAndModify aggregate'; |
| 8 | 1 | var methods = names.split(' '); |
| 9 | ||
| 10 | /** | |
| 11 | * Collection base class from which implementations inherit | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | function Collection () {} |
| 15 | ||
| 16 | 1 | for (var i = 0, len = methods.length; i < len; ++i) { |
| 17 | 8 | var method = methods[i]; |
| 18 | 8 | Collection.prototype[method] = notImplemented(method); |
| 19 | } | |
| 20 | ||
| 21 | 1 | module.exports = exports = Collection; |
| 22 | 1 | Collection.methods = methods; |
| 23 | ||
| 24 | /** | |
| 25 | * creates a function which throws an implementation error | |
| 26 | */ | |
| 27 | ||
| 28 | 1 | function notImplemented (method) { |
| 29 | 8 | return function () { |
| 30 | 0 | throw new Error('collection.' + method + ' not implemented'); |
| 31 | } | |
| 32 | } | |
| 33 | ||
| 34 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var env = require('../env') |
| 4 | ||
| 5 | 1 | if ('unknown' == env.type) { |
| 6 | 0 | throw new Error('Unknown environment') |
| 7 | } | |
| 8 | ||
| 9 | 1 | module.exports = |
| 10 | env.isNode ? require('./node') : | |
| 11 | env.isMongo ? require('./mongo') : | |
| 12 | require('./browser'); | |
| 13 | ||
| 14 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /** | |
| 4 | * Module dependencies | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var Collection = require('./collection'); |
| 8 | 1 | var utils = require('../utils'); |
| 9 | ||
| 10 | 1 | function NodeCollection (col) { |
| 11 | 0 | this.collection = col; |
| 12 | } | |
| 13 | ||
| 14 | /** | |
| 15 | * inherit from collection base class | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | utils.inherits(NodeCollection, Collection); |
| 19 | ||
| 20 | /** | |
| 21 | * find(match, options, function(err, docs)) | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | NodeCollection.prototype.find = function (match, options, cb) { |
| 25 | 0 | this.collection.find(match, options, function (err, cursor) { |
| 26 | 0 | if (err) return cb(err); |
| 27 | ||
| 28 | 0 | cursor.toArray(cb); |
| 29 | }); | |
| 30 | } | |
| 31 | ||
| 32 | /** | |
| 33 | * findOne(match, options, function(err, doc)) | |
| 34 | */ | |
| 35 | ||
| 36 | 1 | NodeCollection.prototype.findOne = function (match, options, cb) { |
| 37 | 0 | this.collection.findOne(match, options, cb); |
| 38 | } | |
| 39 | ||
| 40 | /** | |
| 41 | * count(match, options, function(err, count)) | |
| 42 | */ | |
| 43 | ||
| 44 | 1 | NodeCollection.prototype.count = function (match, options, cb) { |
| 45 | 0 | this.collection.count(match, options, cb); |
| 46 | } | |
| 47 | ||
| 48 | /** | |
| 49 | * distinct(prop, match, options, function(err, count)) | |
| 50 | */ | |
| 51 | ||
| 52 | 1 | NodeCollection.prototype.distinct = function (prop, match, options, cb) { |
| 53 | 0 | this.collection.distinct(prop, match, options, cb); |
| 54 | } | |
| 55 | ||
| 56 | /** | |
| 57 | * update(match, update, options, function(err[, result])) | |
| 58 | */ | |
| 59 | ||
| 60 | 1 | NodeCollection.prototype.update = function (match, update, options, cb) { |
| 61 | 0 | this.collection.update(match, update, options, cb); |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| 65 | * remove(match, options, function(err[, result]) | |
| 66 | */ | |
| 67 | ||
| 68 | 1 | NodeCollection.prototype.remove = function (match, options, cb) { |
| 69 | 0 | this.collection.remove(match, options, cb); |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * findAndModify(match, update, options, function(err, doc)) | |
| 74 | */ | |
| 75 | ||
| 76 | 1 | NodeCollection.prototype.findAndModify = function (match, update, options, cb) { |
| 77 | 0 | var sort = Array.isArray(options.sort) ? options.sort : []; |
| 78 | 0 | this.collection.findAndModify(match, sort, update, options, cb); |
| 79 | } | |
| 80 | ||
| 81 | /** | |
| 82 | * aggregation(operators..., function(err, doc)) | |
| 83 | * TODO | |
| 84 | */ | |
| 85 | ||
| 86 | /** | |
| 87 | * Streams | |
| 88 | * TODO | |
| 89 | */ | |
| 90 | ||
| 91 | /** | |
| 92 | * Expose | |
| 93 | */ | |
| 94 | ||
| 95 | 1 | module.exports = exports = NodeCollection; |
| 96 | ||
| 97 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | exports.isNode = 'undefined' != typeof process |
| 4 | && 'object' == typeof module | |
| 5 | && 'object' == typeof global | |
| 6 | && 'function' == typeof Buffer | |
| 7 | && process.argv | |
| 8 | ||
| 9 | 1 | exports.isMongo = !exports.isNode |
| 10 | && 'function' == typeof printjson | |
| 11 | && 'function' == typeof ObjectId | |
| 12 | && 'function' == typeof rs | |
| 13 | && 'function' == typeof sh; | |
| 14 | ||
| 15 | 1 | exports.isBrowser = !exports.isNode |
| 16 | && !exports.isMongo | |
| 17 | && 'undefined' != typeof window; | |
| 18 | ||
| 19 | 1 | exports.type = exports.isNode ? 'node' |
| 20 | : exports.isMongo ? 'mongo' | |
| 21 | : exports.isBrowser ? 'browser' | |
| 22 | : 'unknown' | |
| 23 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /** | |
| 4 | * Dependencies | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var slice = require('sliced') |
| 8 | 1 | var assert = require('assert') |
| 9 | 1 | var util = require('util') |
| 10 | 1 | var utils = require('./utils') |
| 11 | 1 | var debug = require('debug')('mquery'); |
| 12 | ||
| 13 | /** | |
| 14 | * Query constructor used for building queries. | |
| 15 | * | |
| 16 | * ####Example: | |
| 17 | * | |
| 18 | * var query = new Query({ name: 'mquery' }); | |
| 19 | * query.setOptions({ collection: moduleCollection }) | |
| 20 | * query.where('age').gte(21).exec(callback); | |
| 21 | * | |
| 22 | * @param {Object} [criteria] | |
| 23 | * @param {Object} [options] | |
| 24 | * @api public | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | function Query (criteria, options) { |
| 28 | 1 | if (!(this instanceof Query)) |
| 29 | 0 | return new Query(criteria, options); |
| 30 | ||
| 31 | 1 | var proto = this.constructor.prototype; |
| 32 | ||
| 33 | 1 | this.op = proto.op || undefined; |
| 34 | ||
| 35 | 1 | this.options = {}; |
| 36 | 1 | this.setOptions(proto.options); |
| 37 | ||
| 38 | 1 | this._conditions = proto._conditions |
| 39 | ? utils.clone(proto._conditions) | |
| 40 | : {}; | |
| 41 | ||
| 42 | 1 | this._fields = proto._fields |
| 43 | ? utils.clone(proto._fields) | |
| 44 | : undefined; | |
| 45 | ||
| 46 | 1 | this._update = proto._update |
| 47 | ? utils.clone(proto._update) | |
| 48 | : undefined; | |
| 49 | ||
| 50 | 1 | this._path = proto._path || undefined; |
| 51 | 1 | this._distinct = proto._distinct || undefined; |
| 52 | 1 | this._collection = proto._collection || undefined; |
| 53 | ||
| 54 | 1 | if (options) { |
| 55 | 0 | this.setOptions(options); |
| 56 | } | |
| 57 | ||
| 58 | 1 | if (criteria) { |
| 59 | 0 | if (criteria.find && criteria.remove && criteria.update) { |
| 60 | // quack quack! | |
| 61 | 0 | this.collection(criteria); |
| 62 | } else { | |
| 63 | 0 | this.find(criteria); |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | /** | |
| 69 | * This is a parameter that the user can set which determines if mquery | |
| 70 | * uses $within or $geoWithin for queries. It defaults to true which | |
| 71 | * means $geoWithin will be used. If using MongoDB < 2.4 you should | |
| 72 | * set this to false. | |
| 73 | * | |
| 74 | * @api public | |
| 75 | * @property use$geoWithin | |
| 76 | */ | |
| 77 | ||
| 78 | 1 | var $withinCmd = '$geoWithin'; |
| 79 | 1 | Object.defineProperty(Query, 'use$geoWithin', { |
| 80 | 1 | get: function ( ) { return $withinCmd == '$geoWithin' } |
| 81 | , set: function (v) { | |
| 82 | 0 | if (true === v) { |
| 83 | // mongodb >= 2.4 | |
| 84 | 0 | $withinCmd = '$geoWithin'; |
| 85 | } else { | |
| 86 | 0 | $withinCmd = '$within'; |
| 87 | } | |
| 88 | } | |
| 89 | }); | |
| 90 | ||
| 91 | /** | |
| 92 | * Converts this query to a constructor function with all arguments and options retained. | |
| 93 | * | |
| 94 | * ####Example | |
| 95 | * | |
| 96 | * // Create a query that will read documents with a "video" category from | |
| 97 | * // `aCollection` on the primary node in the replica-set unless it is down, | |
| 98 | * // in which case we'll read from a secondary node. | |
| 99 | * var query = mquery({ category: 'video' }) | |
| 100 | * query.setOptions({ collection: aCollection, read: 'primaryPreferred' }); | |
| 101 | * | |
| 102 | * // create a constructor based off these settings | |
| 103 | * var Video = query.toConstructor(); | |
| 104 | * | |
| 105 | * // Video is now a subclass of mquery() and works the same way but with the | |
| 106 | * // default query parameters and options set. | |
| 107 | * | |
| 108 | * // run a query with the previous settings but filter for movies with names | |
| 109 | * // that start with "Life". | |
| 110 | * Video().where({ name: /^Life/ }).exec(cb); | |
| 111 | * | |
| 112 | * @return {Query} new Query | |
| 113 | * @api public | |
| 114 | */ | |
| 115 | ||
| 116 | 1 | Query.prototype.toConstructor = function toConstructor () { |
| 117 | 0 | function CustomQuery (criteria, options) { |
| 118 | 0 | if (!(this instanceof CustomQuery)) |
| 119 | 0 | return new CustomQuery(criteria, options); |
| 120 | 0 | Query.call(this, criteria, options); |
| 121 | } | |
| 122 | ||
| 123 | 0 | utils.inherits(CustomQuery, Query); |
| 124 | ||
| 125 | // set inherited defaults | |
| 126 | 0 | var p = CustomQuery.prototype; |
| 127 | ||
| 128 | 0 | p.options = {}; |
| 129 | 0 | p.setOptions(this.options); |
| 130 | ||
| 131 | 0 | p.op = this.op; |
| 132 | 0 | p._conditions = utils.clone(this._conditions); |
| 133 | 0 | p._fields = utils.clone(this._fields); |
| 134 | 0 | p._update = utils.clone(this._update); |
| 135 | 0 | p._path = this._path; |
| 136 | 0 | p._distict = this._distinct; |
| 137 | 0 | p._collection = this._collection; |
| 138 | ||
| 139 | 0 | return CustomQuery; |
| 140 | } | |
| 141 | ||
| 142 | /** | |
| 143 | * Sets query options. | |
| 144 | * | |
| 145 | * ####Options: | |
| 146 | * | |
| 147 | * - [tailable](http://www.mongodb.org/display/DOCS/Tailable+Cursors) * | |
| 148 | * - [sort](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsort(\)%7D%7D) * | |
| 149 | * - [limit](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D) * | |
| 150 | * - [skip](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D) * | |
| 151 | * - [maxScan](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24maxScan) * | |
| 152 | * - [batchSize](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D) * | |
| 153 | * - [comment](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment) * | |
| 154 | * - [snapshot](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D) * | |
| 155 | * - [hint](http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint) * | |
| 156 | * - [slaveOk](http://docs.mongodb.org/manual/applications/replication/#read-preference) * | |
| 157 | * - [safe](http://www.mongodb.org/display/DOCS/getLastError+Command) | |
| 158 | * - collection the collection to query against | |
| 159 | * | |
| 160 | * _* denotes a query helper method is also available_ | |
| 161 | * | |
| 162 | * @param {Object} options | |
| 163 | * @api public | |
| 164 | */ | |
| 165 | ||
| 166 | 1 | Query.prototype.setOptions = function (options) { |
| 167 | 1 | if (!(options && utils.isObject(options))) |
| 168 | 1 | return this; |
| 169 | ||
| 170 | // set arbitrary options | |
| 171 | 0 | var methods = utils.keys(options) |
| 172 | , method | |
| 173 | ||
| 174 | 0 | for (var i = 0; i < methods.length; ++i) { |
| 175 | 0 | method = methods[i]; |
| 176 | ||
| 177 | // use methods if exist (safer option manipulation) | |
| 178 | 0 | if ('function' == typeof this[method]) { |
| 179 | 0 | var args = utils.isArray(options[method]) |
| 180 | ? options[method] | |
| 181 | : [options[method]]; | |
| 182 | 0 | this[method].apply(this, args) |
| 183 | } else { | |
| 184 | 0 | this.options[method] = options[method]; |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | 0 | return this; |
| 189 | } | |
| 190 | ||
| 191 | /** | |
| 192 | * Sets this Querys collection. | |
| 193 | * | |
| 194 | * @param {Collection} coll | |
| 195 | * @return {Query} this | |
| 196 | */ | |
| 197 | ||
| 198 | 1 | Query.prototype.collection = function collection (coll) { |
| 199 | 0 | this._collection = new Query.Collection(coll); |
| 200 | ||
| 201 | 0 | return this; |
| 202 | } | |
| 203 | ||
| 204 | /** | |
| 205 | * Specifies a `$where` condition | |
| 206 | * | |
| 207 | * Use `$where` when you need to select documents using a JavaScript expression. | |
| 208 | * | |
| 209 | * ####Example | |
| 210 | * | |
| 211 | * query.$where('this.comments.length > 10 || this.name.length > 5') | |
| 212 | * | |
| 213 | * query.$where(function () { | |
| 214 | * return this.comments.length > 10 || this.name.length > 5; | |
| 215 | * }) | |
| 216 | * | |
| 217 | * @param {String|Function} js javascript string or function | |
| 218 | * @return {Query} this | |
| 219 | * @memberOf Query | |
| 220 | * @method $where | |
| 221 | * @api public | |
| 222 | */ | |
| 223 | ||
| 224 | 1 | Query.prototype.$where = function (js) { |
| 225 | 0 | this._conditions.$where = js; |
| 226 | 0 | return this; |
| 227 | } | |
| 228 | ||
| 229 | /** | |
| 230 | * Specifies a `path` for use with chaining. | |
| 231 | * | |
| 232 | * ####Example | |
| 233 | * | |
| 234 | * // instead of writing: | |
| 235 | * User.find({age: {$gte: 21, $lte: 65}}, callback); | |
| 236 | * | |
| 237 | * // we can instead write: | |
| 238 | * User.where('age').gte(21).lte(65); | |
| 239 | * | |
| 240 | * // passing query conditions is permitted | |
| 241 | * User.find().where({ name: 'vonderful' }) | |
| 242 | * | |
| 243 | * // chaining | |
| 244 | * User | |
| 245 | * .where('age').gte(21).lte(65) | |
| 246 | * .where('name', /^vonderful/i) | |
| 247 | * .where('friends').slice(10) | |
| 248 | * .exec(callback) | |
| 249 | * | |
| 250 | * @param {String} [path] | |
| 251 | * @param {Object} [val] | |
| 252 | * @return {Query} this | |
| 253 | * @api public | |
| 254 | */ | |
| 255 | ||
| 256 | 1 | Query.prototype.where = function () { |
| 257 | 0 | if (!arguments.length) return this; |
| 258 | 0 | if (!this.op) this.op = 'find'; |
| 259 | ||
| 260 | 0 | var type = typeof arguments[0]; |
| 261 | ||
| 262 | 0 | if ('string' == type) { |
| 263 | 0 | this._path = arguments[0]; |
| 264 | ||
| 265 | 0 | if (2 === arguments.length) { |
| 266 | 0 | this._conditions[this._path] = arguments[1]; |
| 267 | } | |
| 268 | ||
| 269 | 0 | return this; |
| 270 | } | |
| 271 | ||
| 272 | 0 | if ('object' == type && !Array.isArray(arguments[0])) { |
| 273 | 0 | return this.merge(arguments[0]); |
| 274 | } | |
| 275 | ||
| 276 | 0 | throw new TypeError('path must be a string or object'); |
| 277 | } | |
| 278 | ||
| 279 | /** | |
| 280 | * Specifies the complementary comparison value for paths specified with `where()` | |
| 281 | * | |
| 282 | * ####Example | |
| 283 | * | |
| 284 | * User.where('age').equals(49); | |
| 285 | * | |
| 286 | * // is the same as | |
| 287 | * | |
| 288 | * User.where('age', 49); | |
| 289 | * | |
| 290 | * @param {Object} val | |
| 291 | * @return {Query} this | |
| 292 | * @api public | |
| 293 | */ | |
| 294 | ||
| 295 | 1 | Query.prototype.equals = function equals (val) { |
| 296 | 0 | this._ensurePath('equals'); |
| 297 | 0 | var path = this._path; |
| 298 | 0 | this._conditions[path] = val; |
| 299 | 0 | return this; |
| 300 | } | |
| 301 | ||
| 302 | /** | |
| 303 | * Specifies arguments for an `$or` condition. | |
| 304 | * | |
| 305 | * ####Example | |
| 306 | * | |
| 307 | * query.or([{ color: 'red' }, { status: 'emergency' }]) | |
| 308 | * | |
| 309 | * @param {Array} array array of conditions | |
| 310 | * @return {Query} this | |
| 311 | * @api public | |
| 312 | */ | |
| 313 | ||
| 314 | 1 | Query.prototype.or = function or (array) { |
| 315 | 0 | var or = this._conditions.$or || (this._conditions.$or = []); |
| 316 | 0 | if (!utils.isArray(array)) array = [array]; |
| 317 | 0 | or.push.apply(or, array); |
| 318 | 0 | return this; |
| 319 | } | |
| 320 | ||
| 321 | /** | |
| 322 | * Specifies arguments for a `$nor` condition. | |
| 323 | * | |
| 324 | * ####Example | |
| 325 | * | |
| 326 | * query.nor([{ color: 'green' }, { status: 'ok' }]) | |
| 327 | * | |
| 328 | * @param {Array} array array of conditions | |
| 329 | * @return {Query} this | |
| 330 | * @api public | |
| 331 | */ | |
| 332 | ||
| 333 | 1 | Query.prototype.nor = function nor (array) { |
| 334 | 0 | var nor = this._conditions.$nor || (this._conditions.$nor = []); |
| 335 | 0 | if (!utils.isArray(array)) array = [array]; |
| 336 | 0 | nor.push.apply(nor, array); |
| 337 | 0 | return this; |
| 338 | } | |
| 339 | ||
| 340 | /** | |
| 341 | * Specifies arguments for a `$and` condition. | |
| 342 | * | |
| 343 | * ####Example | |
| 344 | * | |
| 345 | * query.and([{ color: 'green' }, { status: 'ok' }]) | |
| 346 | * | |
| 347 | * @see $and http://docs.mongodb.org/manual/reference/operator/and/ | |
| 348 | * @param {Array} array array of conditions | |
| 349 | * @return {Query} this | |
| 350 | * @api public | |
| 351 | */ | |
| 352 | ||
| 353 | 1 | Query.prototype.and = function and (array) { |
| 354 | 0 | var and = this._conditions.$and || (this._conditions.$and = []); |
| 355 | 0 | if (!Array.isArray(array)) array = [array]; |
| 356 | 0 | and.push.apply(and, array); |
| 357 | 0 | return this; |
| 358 | } | |
| 359 | ||
| 360 | /** | |
| 361 | * Specifies a $gt query condition. | |
| 362 | * | |
| 363 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 364 | * | |
| 365 | * ####Example | |
| 366 | * | |
| 367 | * Thing.find().where('age').gt(21) | |
| 368 | * | |
| 369 | * // or | |
| 370 | * Thing.find().gt('age', 21) | |
| 371 | * | |
| 372 | * @method gt | |
| 373 | * @memberOf Query | |
| 374 | * @param {String} [path] | |
| 375 | * @param {Number} val | |
| 376 | * @api public | |
| 377 | */ | |
| 378 | ||
| 379 | /** | |
| 380 | * Specifies a $gte query condition. | |
| 381 | * | |
| 382 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 383 | * | |
| 384 | * @method gte | |
| 385 | * @memberOf Query | |
| 386 | * @param {String} [path] | |
| 387 | * @param {Number} val | |
| 388 | * @api public | |
| 389 | */ | |
| 390 | ||
| 391 | /** | |
| 392 | * Specifies a $lt query condition. | |
| 393 | * | |
| 394 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 395 | * | |
| 396 | * @method lt | |
| 397 | * @memberOf Query | |
| 398 | * @param {String} [path] | |
| 399 | * @param {Number} val | |
| 400 | * @api public | |
| 401 | */ | |
| 402 | ||
| 403 | /** | |
| 404 | * Specifies a $lte query condition. | |
| 405 | * | |
| 406 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 407 | * | |
| 408 | * @method lte | |
| 409 | * @memberOf Query | |
| 410 | * @param {String} [path] | |
| 411 | * @param {Number} val | |
| 412 | * @api public | |
| 413 | */ | |
| 414 | ||
| 415 | /** | |
| 416 | * Specifies a $ne query condition. | |
| 417 | * | |
| 418 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 419 | * | |
| 420 | * @method ne | |
| 421 | * @memberOf Query | |
| 422 | * @param {String} [path] | |
| 423 | * @param {Number} val | |
| 424 | * @api public | |
| 425 | */ | |
| 426 | ||
| 427 | /** | |
| 428 | * Specifies an $in query condition. | |
| 429 | * | |
| 430 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 431 | * | |
| 432 | * @method in | |
| 433 | * @memberOf Query | |
| 434 | * @param {String} [path] | |
| 435 | * @param {Number} val | |
| 436 | * @api public | |
| 437 | */ | |
| 438 | ||
| 439 | /** | |
| 440 | * Specifies an $nin query condition. | |
| 441 | * | |
| 442 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 443 | * | |
| 444 | * @method nin | |
| 445 | * @memberOf Query | |
| 446 | * @param {String} [path] | |
| 447 | * @param {Number} val | |
| 448 | * @api public | |
| 449 | */ | |
| 450 | ||
| 451 | /** | |
| 452 | * Specifies an $all query condition. | |
| 453 | * | |
| 454 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 455 | * | |
| 456 | * @method all | |
| 457 | * @memberOf Query | |
| 458 | * @param {String} [path] | |
| 459 | * @param {Number} val | |
| 460 | * @api public | |
| 461 | */ | |
| 462 | ||
| 463 | /** | |
| 464 | * Specifies a $size query condition. | |
| 465 | * | |
| 466 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 467 | * | |
| 468 | * @method size | |
| 469 | * @memberOf Query | |
| 470 | * @param {String} [path] | |
| 471 | * @param {Number} val | |
| 472 | * @api public | |
| 473 | */ | |
| 474 | ||
| 475 | /** | |
| 476 | * Specifies a $regex query condition. | |
| 477 | * | |
| 478 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 479 | * | |
| 480 | * @method regex | |
| 481 | * @memberOf Query | |
| 482 | * @param {String} [path] | |
| 483 | * @param {Number} val | |
| 484 | * @api public | |
| 485 | */ | |
| 486 | ||
| 487 | /** | |
| 488 | * Specifies a $maxDistance query condition. | |
| 489 | * | |
| 490 | * When called with one argument, the most recent path passed to `where()` is used. | |
| 491 | * | |
| 492 | * @method maxDistance | |
| 493 | * @memberOf Query | |
| 494 | * @param {String} [path] | |
| 495 | * @param {Number} val | |
| 496 | * @api public | |
| 497 | */ | |
| 498 | ||
| 499 | /*! | |
| 500 | * gt, gte, lt, lte, ne, in, nin, all, regex, size, maxDistance | |
| 501 | * | |
| 502 | * Thing.where('type').nin(array) | |
| 503 | */ | |
| 504 | ||
| 505 | 1 | 'gt gte lt lte ne in nin all regex size maxDistance'.split(' ').forEach(function ($conditional) { |
| 506 | 11 | Query.prototype[$conditional] = function () { |
| 507 | 0 | var path, val; |
| 508 | ||
| 509 | 0 | if (1 === arguments.length) { |
| 510 | 0 | this._ensurePath($conditional); |
| 511 | 0 | val = arguments[0]; |
| 512 | 0 | path = this._path; |
| 513 | } else { | |
| 514 | 0 | val = arguments[1]; |
| 515 | 0 | path = arguments[0]; |
| 516 | } | |
| 517 | ||
| 518 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 519 | 0 | conds['$' + $conditional] = val; |
| 520 | 0 | return this; |
| 521 | }; | |
| 522 | }) | |
| 523 | ||
| 524 | /** | |
| 525 | * Specifies a `$mod` condition | |
| 526 | * | |
| 527 | * @param {String} [path] | |
| 528 | * @param {Number} val | |
| 529 | * @return {Query} this | |
| 530 | * @api public | |
| 531 | */ | |
| 532 | ||
| 533 | 1 | Query.prototype.mod = function () { |
| 534 | 0 | var val, path; |
| 535 | ||
| 536 | 0 | if (1 === arguments.length) { |
| 537 | 0 | this._ensurePath('mod') |
| 538 | 0 | val = arguments[0]; |
| 539 | 0 | path = this._path; |
| 540 | 0 | } else if (2 === arguments.length && !utils.isArray(arguments[1])) { |
| 541 | 0 | this._ensurePath('mod') |
| 542 | 0 | val = slice(arguments); |
| 543 | 0 | path = this._path; |
| 544 | 0 | } else if (3 === arguments.length) { |
| 545 | 0 | val = slice(arguments, 1); |
| 546 | 0 | path = arguments[0]; |
| 547 | } else { | |
| 548 | 0 | val = arguments[1]; |
| 549 | 0 | path = arguments[0]; |
| 550 | } | |
| 551 | ||
| 552 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 553 | 0 | conds.$mod = val; |
| 554 | 0 | return this; |
| 555 | } | |
| 556 | ||
| 557 | /** | |
| 558 | * Specifies an `$exists` condition | |
| 559 | * | |
| 560 | * ####Example | |
| 561 | * | |
| 562 | * // { name: { $exists: true }} | |
| 563 | * Thing.where('name').exists() | |
| 564 | * Thing.where('name').exists(true) | |
| 565 | * Thing.find().exists('name') | |
| 566 | * | |
| 567 | * // { name: { $exists: false }} | |
| 568 | * Thing.where('name').exists(false); | |
| 569 | * Thing.find().exists('name', false); | |
| 570 | * | |
| 571 | * @param {String} [path] | |
| 572 | * @param {Number} val | |
| 573 | * @return {Query} this | |
| 574 | * @api public | |
| 575 | */ | |
| 576 | ||
| 577 | 1 | Query.prototype.exists = function () { |
| 578 | 0 | var path, val; |
| 579 | ||
| 580 | 0 | if (0 === arguments.length) { |
| 581 | 0 | this._ensurePath('exists'); |
| 582 | 0 | path = this._path; |
| 583 | 0 | val = true; |
| 584 | 0 | } else if (1 === arguments.length) { |
| 585 | 0 | if ('boolean' === typeof arguments[0]) { |
| 586 | 0 | this._ensurePath('exists'); |
| 587 | 0 | path = this._path; |
| 588 | 0 | val = arguments[0]; |
| 589 | } else { | |
| 590 | 0 | path = arguments[0]; |
| 591 | 0 | val = true; |
| 592 | } | |
| 593 | 0 | } else if (2 === arguments.length) { |
| 594 | 0 | path = arguments[0]; |
| 595 | 0 | val = arguments[1]; |
| 596 | } | |
| 597 | ||
| 598 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 599 | 0 | conds.$exists = val; |
| 600 | 0 | return this; |
| 601 | } | |
| 602 | ||
| 603 | /** | |
| 604 | * Specifies an `$elemMatch` condition | |
| 605 | * | |
| 606 | * ####Example | |
| 607 | * | |
| 608 | * query.elemMatch('comment', { author: 'autobot', votes: {$gte: 5}}) | |
| 609 | * | |
| 610 | * query.where('comment').elemMatch({ author: 'autobot', votes: {$gte: 5}}) | |
| 611 | * | |
| 612 | * query.elemMatch('comment', function (elem) { | |
| 613 | * elem.where('author').equals('autobot'); | |
| 614 | * elem.where('votes').gte(5); | |
| 615 | * }) | |
| 616 | * | |
| 617 | * query.where('comment').elemMatch(function (elem) { | |
| 618 | * elem.where({ author: 'autobot' }); | |
| 619 | * elem.where('votes').gte(5); | |
| 620 | * }) | |
| 621 | * | |
| 622 | * @param {String|Object|Function} path | |
| 623 | * @param {Object|Function} criteria | |
| 624 | * @return {Query} this | |
| 625 | * @api public | |
| 626 | */ | |
| 627 | ||
| 628 | 1 | Query.prototype.elemMatch = function () { |
| 629 | 0 | if (null == arguments[0]) |
| 630 | 0 | throw new TypeError("Invalid argument"); |
| 631 | ||
| 632 | 0 | var fn, path, criteria; |
| 633 | ||
| 634 | 0 | if ('function' === typeof arguments[0]) { |
| 635 | 0 | this._ensurePath('elemMatch'); |
| 636 | 0 | path = this._path; |
| 637 | 0 | fn = arguments[0]; |
| 638 | 0 | } else if (utils.isObject(arguments[0])) { |
| 639 | 0 | this._ensurePath('elemMatch'); |
| 640 | 0 | path = this._path; |
| 641 | 0 | criteria = arguments[0]; |
| 642 | 0 | } else if ('function' === typeof arguments[1]) { |
| 643 | 0 | path = arguments[0]; |
| 644 | 0 | fn = arguments[1]; |
| 645 | 0 | } else if (arguments[1] && utils.isObject(arguments[1])) { |
| 646 | 0 | path = arguments[0]; |
| 647 | 0 | criteria = arguments[1]; |
| 648 | } else { | |
| 649 | 0 | throw new TypeError("Invalid argument"); |
| 650 | } | |
| 651 | ||
| 652 | 0 | if (fn) { |
| 653 | 0 | criteria = new Query; |
| 654 | 0 | fn(criteria); |
| 655 | 0 | criteria = criteria._conditions; |
| 656 | } | |
| 657 | ||
| 658 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 659 | 0 | conds.$elemMatch = criteria; |
| 660 | 0 | return this; |
| 661 | } | |
| 662 | ||
| 663 | // Spatial queries | |
| 664 | ||
| 665 | /** | |
| 666 | * Sugar for geo-spatial queries. | |
| 667 | * | |
| 668 | * ####Example | |
| 669 | * | |
| 670 | * query.within().box() | |
| 671 | * query.within().circle() | |
| 672 | * query.within().geometry() | |
| 673 | * | |
| 674 | * query.where('loc').within({ center: [50,50], radius: 10, unique: true, spherical: true }); | |
| 675 | * query.where('loc').within({ box: [[40.73, -73.9], [40.7, -73.988]] }); | |
| 676 | * query.where('loc').within({ polygon: [[],[],[],[]] }); | |
| 677 | * | |
| 678 | * query.where('loc').within([], [], []) // polygon | |
| 679 | * query.where('loc').within([], []) // box | |
| 680 | * query.where('loc').within({ type: 'LineString', coordinates: [...] }); // geometry | |
| 681 | * | |
| 682 | * ####NOTE: | |
| 683 | * | |
| 684 | * Must be used after `where()`. | |
| 685 | * | |
| 686 | * @memberOf Query | |
| 687 | * @return {Query} this | |
| 688 | * @api public | |
| 689 | */ | |
| 690 | ||
| 691 | 1 | Query.prototype.within = function within () { |
| 692 | // opinionated, must be used after where | |
| 693 | 0 | this._ensurePath('within'); |
| 694 | 0 | this._geoComparison = $withinCmd; |
| 695 | ||
| 696 | 0 | if (0 === arguments.length) { |
| 697 | 0 | return this; |
| 698 | } | |
| 699 | ||
| 700 | 0 | if (2 === arguments.length) { |
| 701 | 0 | return this.box.apply(this, arguments); |
| 702 | 0 | } else if (2 < arguments.length) { |
| 703 | 0 | return this.polygon.apply(this, arguments); |
| 704 | } | |
| 705 | ||
| 706 | 0 | var area = arguments[0]; |
| 707 | ||
| 708 | 0 | if (!area) |
| 709 | 0 | throw new TypeError('Invalid argument'); |
| 710 | ||
| 711 | 0 | if (area.center) |
| 712 | 0 | return this.circle(area); |
| 713 | ||
| 714 | 0 | if (area.box) |
| 715 | 0 | return this.box.apply(this, area.box); |
| 716 | ||
| 717 | 0 | if (area.polygon) |
| 718 | 0 | return this.polygon.apply(this, area.polygon); |
| 719 | ||
| 720 | 0 | if (area.type && area.coordinates) |
| 721 | 0 | return this.geometry(area); |
| 722 | ||
| 723 | 0 | throw new TypeError('Invalid argument'); |
| 724 | } | |
| 725 | ||
| 726 | /** | |
| 727 | * Specifies a $box condition | |
| 728 | * | |
| 729 | * ####Example | |
| 730 | * | |
| 731 | * var lowerLeft = [40.73083, -73.99756] | |
| 732 | * var upperRight= [40.741404, -73.988135] | |
| 733 | * | |
| 734 | * query.where('loc').within().box(lowerLeft, upperRight) | |
| 735 | * query.box('loc', lowerLeft, upperRight ) | |
| 736 | * | |
| 737 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 738 | * @see Query#within #query_Query-within | |
| 739 | * @param {String} path | |
| 740 | * @param {Object} val | |
| 741 | * @return {Query} this | |
| 742 | * @api public | |
| 743 | */ | |
| 744 | ||
| 745 | 1 | Query.prototype.box = function () { |
| 746 | 0 | var path, box; |
| 747 | ||
| 748 | 0 | if (3 === arguments.length) { |
| 749 | // box('loc', [], []) | |
| 750 | 0 | path = arguments[0]; |
| 751 | 0 | box = [arguments[1], arguments[2]]; |
| 752 | 0 | } else if (2 === arguments.length) { |
| 753 | // box([], []) | |
| 754 | 0 | this._ensurePath('box'); |
| 755 | 0 | path = this._path; |
| 756 | 0 | box = [arguments[0], arguments[1]]; |
| 757 | } else { | |
| 758 | 0 | throw new TypeError("Invalid argument"); |
| 759 | } | |
| 760 | ||
| 761 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 762 | 0 | conds[this._geoComparison || $withinCmd] = { '$box': box }; |
| 763 | 0 | return this; |
| 764 | } | |
| 765 | ||
| 766 | /** | |
| 767 | * Specifies a $polygon condition | |
| 768 | * | |
| 769 | * ####Example | |
| 770 | * | |
| 771 | * query.where('loc').within().polygon([10,20], [13, 25], [7,15]) | |
| 772 | * query.polygon('loc', [10,20], [13, 25], [7,15]) | |
| 773 | * | |
| 774 | * @param {String|Array} [path] | |
| 775 | * @param {Array|Object} [val] | |
| 776 | * @return {Query} this | |
| 777 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 778 | * @api public | |
| 779 | */ | |
| 780 | ||
| 781 | 1 | Query.prototype.polygon = function () { |
| 782 | 0 | var val, path; |
| 783 | ||
| 784 | 0 | if ('string' == typeof arguments[0]) { |
| 785 | // polygon('loc', [],[],[]) | |
| 786 | 0 | path = arguments[0]; |
| 787 | 0 | val = slice(arguments, 1); |
| 788 | } else { | |
| 789 | // polygon([],[],[]) | |
| 790 | 0 | this._ensurePath('polygon'); |
| 791 | 0 | path = this._path; |
| 792 | 0 | val = slice(arguments); |
| 793 | } | |
| 794 | ||
| 795 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 796 | 0 | conds[this._geoComparison || $withinCmd] = { '$polygon': val }; |
| 797 | 0 | return this; |
| 798 | } | |
| 799 | ||
| 800 | /** | |
| 801 | * Specifies a $center or $centerSphere condition. | |
| 802 | * | |
| 803 | * ####Example | |
| 804 | * | |
| 805 | * var area = { center: [50, 50], radius: 10, unique: true } | |
| 806 | * query.where('loc').within().circle(area) | |
| 807 | * query.center('loc', area); | |
| 808 | * | |
| 809 | * // for spherical calculations | |
| 810 | * var area = { center: [50, 50], radius: 10, unique: true, spherical: true } | |
| 811 | * query.where('loc').within().circle(area) | |
| 812 | * query.center('loc', area); | |
| 813 | * | |
| 814 | * @param {String} [path] | |
| 815 | * @param {Object} area | |
| 816 | * @return {Query} this | |
| 817 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 818 | * @api public | |
| 819 | */ | |
| 820 | ||
| 821 | 1 | Query.prototype.circle = function () { |
| 822 | 0 | var path, val; |
| 823 | ||
| 824 | 0 | if (1 === arguments.length) { |
| 825 | 0 | this._ensurePath('circle'); |
| 826 | 0 | path = this._path; |
| 827 | 0 | val = arguments[0]; |
| 828 | 0 | } else if (2 === arguments.length) { |
| 829 | 0 | path = arguments[0]; |
| 830 | 0 | val = arguments[1]; |
| 831 | } else { | |
| 832 | 0 | throw new TypeError("Invalid argument"); |
| 833 | } | |
| 834 | ||
| 835 | 0 | if (!('radius' in val && val.center)) |
| 836 | 0 | throw new Error('center and radius are required'); |
| 837 | ||
| 838 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 839 | ||
| 840 | 0 | var type = val.spherical |
| 841 | ? '$centerSphere' | |
| 842 | : '$center'; | |
| 843 | ||
| 844 | 0 | var wKey = this._geoComparison || $withinCmd; |
| 845 | 0 | conds[wKey] = {}; |
| 846 | 0 | conds[wKey][type] = [val.center, val.radius]; |
| 847 | ||
| 848 | 0 | if ('unique' in val) |
| 849 | 0 | conds[wKey].$uniqueDocs = !! val.unique; |
| 850 | ||
| 851 | 0 | return this; |
| 852 | } | |
| 853 | ||
| 854 | /** | |
| 855 | * Specifies a `$near` or `$nearSphere` condition | |
| 856 | * | |
| 857 | * These operators return documents sorted by distance. | |
| 858 | * | |
| 859 | * ####Example | |
| 860 | * | |
| 861 | * query.where('loc').near({ center: [10, 10] }); | |
| 862 | * query.where('loc').near({ center: [10, 10], maxDistance: 5 }); | |
| 863 | * query.where('loc').near({ center: [10, 10], maxDistance: 5, spherical: true }); | |
| 864 | * query.near('loc', { center: [10, 10], maxDistance: 5 }); | |
| 865 | * query.near({ center: { type: 'Point', coordinates: [..] }}) | |
| 866 | * query.near().geometry({ type: 'Point', coordinates: [..] }) | |
| 867 | * | |
| 868 | * @param {String} [path] | |
| 869 | * @param {Object} val | |
| 870 | * @return {Query} this | |
| 871 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 872 | * @api public | |
| 873 | */ | |
| 874 | ||
| 875 | 1 | Query.prototype.near = function near () { |
| 876 | 0 | var path, val; |
| 877 | ||
| 878 | 0 | this._geoComparison = '$near'; |
| 879 | ||
| 880 | 0 | if (0 === arguments.length) { |
| 881 | 0 | return this; |
| 882 | 0 | } else if (1 === arguments.length) { |
| 883 | 0 | this._ensurePath('near'); |
| 884 | 0 | path = this._path; |
| 885 | 0 | val = arguments[0]; |
| 886 | 0 | } else if (2 === arguments.length) { |
| 887 | 0 | path = arguments[0]; |
| 888 | 0 | val = arguments[1]; |
| 889 | } else { | |
| 890 | 0 | throw new TypeError("Invalid argument"); |
| 891 | } | |
| 892 | ||
| 893 | 0 | if (!val.center) { |
| 894 | 0 | throw new Error('center is required'); |
| 895 | } | |
| 896 | ||
| 897 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 898 | ||
| 899 | 0 | var type = val.spherical |
| 900 | ? '$nearSphere' | |
| 901 | : '$near'; | |
| 902 | ||
| 903 | // center could be a GeoJSON object or an Array | |
| 904 | 0 | if (Array.isArray(val.center)) { |
| 905 | 0 | conds[type] = val.center; |
| 906 | } else { | |
| 907 | // GeoJSON? | |
| 908 | 0 | if (val.center.type != 'Point' || !Array.isArray(val.center.coordinates)) { |
| 909 | 0 | throw new Error(util.format("Invalid GeoJSON specified for %s", type)); |
| 910 | } | |
| 911 | 0 | conds[type] = { $geometry : val.center }; |
| 912 | } | |
| 913 | ||
| 914 | 0 | var radius = 'maxDistance' in val |
| 915 | ? val.maxDistance | |
| 916 | : null; | |
| 917 | ||
| 918 | 0 | if (null != radius) { |
| 919 | 0 | conds.$maxDistance = radius; |
| 920 | } | |
| 921 | ||
| 922 | 0 | return this; |
| 923 | } | |
| 924 | ||
| 925 | /** | |
| 926 | * Declares an intersects query for `geometry()`. | |
| 927 | * | |
| 928 | * ####Example | |
| 929 | * | |
| 930 | * query.where('path').intersects().geometry({ | |
| 931 | * type: 'LineString' | |
| 932 | * , coordinates: [[180.0, 11.0], [180, 9.0]] | |
| 933 | * }) | |
| 934 | * | |
| 935 | * query.where('path').intersects({ | |
| 936 | * type: 'LineString' | |
| 937 | * , coordinates: [[180.0, 11.0], [180, 9.0]] | |
| 938 | * }) | |
| 939 | * | |
| 940 | * @param {Object} [arg] | |
| 941 | * @return {Query} this | |
| 942 | * @api public | |
| 943 | */ | |
| 944 | ||
| 945 | 1 | Query.prototype.intersects = function intersects () { |
| 946 | // opinionated, must be used after where | |
| 947 | 0 | this._ensurePath('intersects'); |
| 948 | ||
| 949 | 0 | this._geoComparison = '$geoIntersects'; |
| 950 | ||
| 951 | 0 | if (0 === arguments.length) { |
| 952 | 0 | return this; |
| 953 | } | |
| 954 | ||
| 955 | 0 | var area = arguments[0]; |
| 956 | ||
| 957 | 0 | if (null != area && area.type && area.coordinates) |
| 958 | 0 | return this.geometry(area); |
| 959 | ||
| 960 | 0 | throw new TypeError('Invalid argument'); |
| 961 | } | |
| 962 | ||
| 963 | /** | |
| 964 | * Specifies a `$geometry` condition | |
| 965 | * | |
| 966 | * ####Example | |
| 967 | * | |
| 968 | * var polyA = [[[ 10, 20 ], [ 10, 40 ], [ 30, 40 ], [ 30, 20 ]]] | |
| 969 | * query.where('loc').within().geometry({ type: 'Polygon', coordinates: polyA }) | |
| 970 | * | |
| 971 | * // or | |
| 972 | * var polyB = [[ 0, 0 ], [ 1, 1 ]] | |
| 973 | * query.where('loc').within().geometry({ type: 'LineString', coordinates: polyB }) | |
| 974 | * | |
| 975 | * // or | |
| 976 | * var polyC = [ 0, 0 ] | |
| 977 | * query.where('loc').within().geometry({ type: 'Point', coordinates: polyC }) | |
| 978 | * | |
| 979 | * // or | |
| 980 | * query.where('loc').intersects().geometry({ type: 'Point', coordinates: polyC }) | |
| 981 | * | |
| 982 | * ####NOTE: | |
| 983 | * | |
| 984 | * `geometry()` **must** come after either `intersects()` or `within()`. | |
| 985 | * | |
| 986 | * The `object` argument must contain `type` and `coordinates` properties. | |
| 987 | * - type {String} | |
| 988 | * - coordinates {Array} | |
| 989 | * | |
| 990 | * The most recent path passed to `where()` is used. | |
| 991 | * | |
| 992 | * @param {Object} object Must contain a `type` property which is a String and a `coordinates` property which is an Array. See the examples. | |
| 993 | * @return {Query} this | |
| 994 | * @see http://docs.mongodb.org/manual/release-notes/2.4/#new-geospatial-indexes-with-geojson-and-improved-spherical-geometry | |
| 995 | * @see http://www.mongodb.org/display/DOCS/Geospatial+Indexing | |
| 996 | * @see $geometry http://docs.mongodb.org/manual/reference/operator/geometry/ | |
| 997 | * @api public | |
| 998 | */ | |
| 999 | ||
| 1000 | 1 | Query.prototype.geometry = function geometry () { |
| 1001 | 0 | if (!('$within' == this._geoComparison || |
| 1002 | '$geoWithin' == this._geoComparison || | |
| 1003 | '$near' == this._geoComparison || | |
| 1004 | '$geoIntersects' == this._geoComparison)) { | |
| 1005 | 0 | throw new Error('geometry() must come after `within()`, `intersects()`, or `near()'); |
| 1006 | } | |
| 1007 | ||
| 1008 | 0 | var val, path; |
| 1009 | ||
| 1010 | 0 | if (1 === arguments.length) { |
| 1011 | 0 | this._ensurePath('geometry'); |
| 1012 | 0 | path = this._path; |
| 1013 | 0 | val = arguments[0]; |
| 1014 | } else { | |
| 1015 | 0 | throw new TypeError("Invalid argument"); |
| 1016 | } | |
| 1017 | ||
| 1018 | 0 | if (!(val.type && Array.isArray(val.coordinates))) { |
| 1019 | 0 | throw new TypeError('Invalid argument'); |
| 1020 | } | |
| 1021 | ||
| 1022 | 0 | var conds = this._conditions[path] || (this._conditions[path] = {}); |
| 1023 | 0 | conds[this._geoComparison] = { $geometry: val }; |
| 1024 | ||
| 1025 | 0 | return this; |
| 1026 | } | |
| 1027 | ||
| 1028 | // end spatial | |
| 1029 | ||
| 1030 | /** | |
| 1031 | * Specifies which document fields to include or exclude | |
| 1032 | * | |
| 1033 | * ####String syntax | |
| 1034 | * | |
| 1035 | * When passing a string, prefixing a path with `-` will flag that path as excluded. When a path does not have the `-` prefix, it is included. | |
| 1036 | * | |
| 1037 | * ####Example | |
| 1038 | * | |
| 1039 | * // include a and b, exclude c | |
| 1040 | * query.select('a b -c'); | |
| 1041 | * | |
| 1042 | * // or you may use object notation, useful when | |
| 1043 | * // you have keys already prefixed with a "-" | |
| 1044 | * query.select({a: 1, b: 1, c: 0}); | |
| 1045 | * | |
| 1046 | * ####Note | |
| 1047 | * | |
| 1048 | * Cannot be used with `distinct()` | |
| 1049 | * | |
| 1050 | * @param {Object|String} arg | |
| 1051 | * @return {Query} this | |
| 1052 | * @see SchemaType | |
| 1053 | * @api public | |
| 1054 | */ | |
| 1055 | ||
| 1056 | 1 | Query.prototype.select = function select () { |
| 1057 | 0 | var arg = arguments[0]; |
| 1058 | 0 | if (!arg) return this; |
| 1059 | ||
| 1060 | 0 | if (arguments.length !== 1) { |
| 1061 | 0 | throw new Error("Invalid select: select only takes 1 argument"); |
| 1062 | } | |
| 1063 | ||
| 1064 | 0 | this._validate('select'); |
| 1065 | ||
| 1066 | 0 | var fields = this._fields || (this._fields = {}); |
| 1067 | 0 | var type = typeof arg; |
| 1068 | ||
| 1069 | 0 | if ('string' == type || 'object' == type && 'number' == typeof arg.length && !Array.isArray(arg)) { |
| 1070 | 0 | if ('string' == type) |
| 1071 | 0 | arg = arg.split(/\s+/); |
| 1072 | ||
| 1073 | 0 | for (var i = 0, len = arg.length; i < len; ++i) { |
| 1074 | 0 | var field = arg[i]; |
| 1075 | 0 | if (!field) continue; |
| 1076 | 0 | var include = '-' == field[0] ? 0 : 1; |
| 1077 | 0 | if (include === 0) field = field.substring(1); |
| 1078 | 0 | fields[field] = include; |
| 1079 | } | |
| 1080 | ||
| 1081 | 0 | return this; |
| 1082 | } | |
| 1083 | ||
| 1084 | 0 | if (utils.isObject(arg) && !Array.isArray(arg)) { |
| 1085 | 0 | var keys = utils.keys(arg); |
| 1086 | 0 | for (var i = 0; i < keys.length; ++i) { |
| 1087 | 0 | fields[keys[i]] = arg[keys[i]]; |
| 1088 | } | |
| 1089 | 0 | return this; |
| 1090 | } | |
| 1091 | ||
| 1092 | 0 | throw new TypeError('Invalid select() argument. Must be string or object.'); |
| 1093 | } | |
| 1094 | ||
| 1095 | /** | |
| 1096 | * Specifies a $slice condition for a `path` | |
| 1097 | * | |
| 1098 | * ####Example | |
| 1099 | * | |
| 1100 | * query.slice('comments', 5) | |
| 1101 | * query.slice('comments', -5) | |
| 1102 | * query.slice('comments', [10, 5]) | |
| 1103 | * query.where('comments').slice(5) | |
| 1104 | * query.where('comments').slice([-10, 5]) | |
| 1105 | * | |
| 1106 | * @param {String} [path] | |
| 1107 | * @param {Number} val number/range of elements to slice | |
| 1108 | * @return {Query} this | |
| 1109 | * @see mongodb http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-RetrievingaSubrangeofArrayElements | |
| 1110 | * @api public | |
| 1111 | */ | |
| 1112 | ||
| 1113 | 1 | Query.prototype.slice = function () { |
| 1114 | 0 | if (0 === arguments.length) |
| 1115 | 0 | return this; |
| 1116 | ||
| 1117 | 0 | this._validate('slice'); |
| 1118 | ||
| 1119 | 0 | var path, val; |
| 1120 | ||
| 1121 | 0 | if (1 === arguments.length) { |
| 1122 | 0 | this._ensurePath('slice'); |
| 1123 | 0 | path = this._path; |
| 1124 | 0 | val = arguments[0]; |
| 1125 | 0 | } else if (2 === arguments.length) { |
| 1126 | 0 | if ('number' === typeof arguments[0]) { |
| 1127 | 0 | this._ensurePath('slice'); |
| 1128 | 0 | path = this._path; |
| 1129 | 0 | val = slice(arguments); |
| 1130 | } else { | |
| 1131 | 0 | path = arguments[0]; |
| 1132 | 0 | val = arguments[1]; |
| 1133 | } | |
| 1134 | 0 | } else if (3 === arguments.length) { |
| 1135 | 0 | path = arguments[0]; |
| 1136 | 0 | val = slice(arguments, 1); |
| 1137 | } | |
| 1138 | ||
| 1139 | 0 | var myFields = this._fields || (this._fields = {}); |
| 1140 | 0 | myFields[path] = { '$slice': val }; |
| 1141 | 0 | return this; |
| 1142 | } | |
| 1143 | ||
| 1144 | /** | |
| 1145 | * Sets the sort order | |
| 1146 | * | |
| 1147 | * If an object is passed, values allowed are 'asc', 'desc', 'ascending', 'descending', 1, and -1. | |
| 1148 | * | |
| 1149 | * If a string is passed, it must be a space delimited list of path names. The sort order of each path is ascending unless the path name is prefixed with `-` which will be treated as descending. | |
| 1150 | * | |
| 1151 | * ####Example | |
| 1152 | * | |
| 1153 | * // these are equivalent | |
| 1154 | * query.sort({ field: 'asc', test: -1 }); | |
| 1155 | * query.sort('field -test'); | |
| 1156 | * | |
| 1157 | * ####Note | |
| 1158 | * | |
| 1159 | * Cannot be used with `distinct()` | |
| 1160 | * | |
| 1161 | * @param {Object|String} arg | |
| 1162 | * @return {Query} this | |
| 1163 | * @api public | |
| 1164 | */ | |
| 1165 | ||
| 1166 | 1 | Query.prototype.sort = function (arg) { |
| 1167 | 0 | if (!arg) return this; |
| 1168 | ||
| 1169 | 0 | this._validate('sort'); |
| 1170 | ||
| 1171 | 0 | var type = typeof arg; |
| 1172 | ||
| 1173 | 0 | if (1 === arguments.length && 'string' == type) { |
| 1174 | 0 | arg = arg.split(/\s+/); |
| 1175 | ||
| 1176 | 0 | for (var i = 0, len = arg.length; i < len; ++i) { |
| 1177 | 0 | var field = arg[i]; |
| 1178 | 0 | if (!field) continue; |
| 1179 | 0 | var ascend = '-' == field[0] ? -1 : 1; |
| 1180 | 0 | if (ascend === -1) field = field.substring(1); |
| 1181 | 0 | push(this.options, field, ascend); |
| 1182 | } | |
| 1183 | ||
| 1184 | 0 | return this; |
| 1185 | } | |
| 1186 | ||
| 1187 | 0 | if (utils.isObject(arg)) { |
| 1188 | 0 | var keys = utils.keys(arg); |
| 1189 | 0 | for (var i = 0; i < keys.length; ++i) { |
| 1190 | 0 | var field = keys[i]; |
| 1191 | 0 | push(this.options, field, arg[field]); |
| 1192 | } | |
| 1193 | ||
| 1194 | 0 | return this; |
| 1195 | } | |
| 1196 | ||
| 1197 | 0 | throw new TypeError('Invalid sort() argument. Must be a string or object.'); |
| 1198 | } | |
| 1199 | ||
| 1200 | /*! | |
| 1201 | * @ignore | |
| 1202 | */ | |
| 1203 | ||
| 1204 | 1 | function push (opts, field, value) { |
| 1205 | 0 | var val = String(value || 1).toLowerCase(); |
| 1206 | 0 | if (!/^(?:ascending|asc|descending|desc|1|-1)$/.test(val)) { |
| 1207 | 0 | if (utils.isArray(value)) value = '['+value+']'; |
| 1208 | 0 | throw new TypeError('Invalid sort value: {' + field + ': ' + value + ' }'); |
| 1209 | } | |
| 1210 | // store `sort` in a sane format | |
| 1211 | 0 | var s = opts.sort || (opts.sort = {}); |
| 1212 | 0 | var valueStr = value.toString() |
| 1213 | .replace("asc", "1") | |
| 1214 | .replace("ascending", "1") | |
| 1215 | .replace("desc", "-1") | |
| 1216 | .replace("descending", "-1"); | |
| 1217 | 0 | s[field] = parseInt(valueStr); |
| 1218 | } | |
| 1219 | ||
| 1220 | /** | |
| 1221 | * Specifies the limit option. | |
| 1222 | * | |
| 1223 | * ####Example | |
| 1224 | * | |
| 1225 | * query.limit(20) | |
| 1226 | * | |
| 1227 | * ####Note | |
| 1228 | * | |
| 1229 | * Cannot be used with `distinct()` | |
| 1230 | * | |
| 1231 | * @method limit | |
| 1232 | * @memberOf Query | |
| 1233 | * @param {Number} val | |
| 1234 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Blimit%28%29%7D%7D | |
| 1235 | * @api public | |
| 1236 | */ | |
| 1237 | /** | |
| 1238 | * Specifies the skip option. | |
| 1239 | * | |
| 1240 | * ####Example | |
| 1241 | * | |
| 1242 | * query.skip(100).limit(20) | |
| 1243 | * | |
| 1244 | * ####Note | |
| 1245 | * | |
| 1246 | * Cannot be used with `distinct()` | |
| 1247 | * | |
| 1248 | * @method skip | |
| 1249 | * @memberOf Query | |
| 1250 | * @param {Number} val | |
| 1251 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bskip%28%29%7D%7D | |
| 1252 | * @api public | |
| 1253 | */ | |
| 1254 | /** | |
| 1255 | * Specifies the maxScan option. | |
| 1256 | * | |
| 1257 | * ####Example | |
| 1258 | * | |
| 1259 | * query.maxScan(100) | |
| 1260 | * | |
| 1261 | * ####Note | |
| 1262 | * | |
| 1263 | * Cannot be used with `distinct()` | |
| 1264 | * | |
| 1265 | * @method maxScan | |
| 1266 | * @memberOf Query | |
| 1267 | * @param {Number} val | |
| 1268 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24maxScan | |
| 1269 | * @api public | |
| 1270 | */ | |
| 1271 | /** | |
| 1272 | * Specifies the batchSize option. | |
| 1273 | * | |
| 1274 | * ####Example | |
| 1275 | * | |
| 1276 | * query.batchSize(100) | |
| 1277 | * | |
| 1278 | * ####Note | |
| 1279 | * | |
| 1280 | * Cannot be used with `distinct()` | |
| 1281 | * | |
| 1282 | * @method batchSize | |
| 1283 | * @memberOf Query | |
| 1284 | * @param {Number} val | |
| 1285 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7BbatchSize%28%29%7D%7D | |
| 1286 | * @api public | |
| 1287 | */ | |
| 1288 | /** | |
| 1289 | * Specifies the `comment` option. | |
| 1290 | * | |
| 1291 | * ####Example | |
| 1292 | * | |
| 1293 | * query.comment('login query') | |
| 1294 | * | |
| 1295 | * ####Note | |
| 1296 | * | |
| 1297 | * Cannot be used with `distinct()` | |
| 1298 | * | |
| 1299 | * @method comment | |
| 1300 | * @memberOf Query | |
| 1301 | * @param {Number} val | |
| 1302 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24comment | |
| 1303 | * @api public | |
| 1304 | */ | |
| 1305 | ||
| 1306 | /*! | |
| 1307 | * limit, skip, maxScan, batchSize, comment | |
| 1308 | * | |
| 1309 | * Sets these associated options. | |
| 1310 | * | |
| 1311 | * query.comment('feed query'); | |
| 1312 | */ | |
| 1313 | ||
| 1314 | 1 | ;['limit', 'skip', 'maxScan', 'batchSize', 'comment'].forEach(function (method) { |
| 1315 | 5 | Query.prototype[method] = function (v) { |
| 1316 | 0 | this._validate(method); |
| 1317 | 0 | this.options[method] = v; |
| 1318 | 0 | return this; |
| 1319 | }; | |
| 1320 | }) | |
| 1321 | ||
| 1322 | /** | |
| 1323 | * Specifies this query as a `snapshot` query. | |
| 1324 | * | |
| 1325 | * ####Example | |
| 1326 | * | |
| 1327 | * mquery().snapshot() // true | |
| 1328 | * mquery().snapshot(true) | |
| 1329 | * mquery().snapshot(false) | |
| 1330 | * | |
| 1331 | * ####Note | |
| 1332 | * | |
| 1333 | * Cannot be used with `distinct()` | |
| 1334 | * | |
| 1335 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%7B%7Bsnapshot%28%29%7D%7D | |
| 1336 | * @return {Query} this | |
| 1337 | * @api public | |
| 1338 | */ | |
| 1339 | ||
| 1340 | 1 | Query.prototype.snapshot = function () { |
| 1341 | 0 | this._validate('snapshot'); |
| 1342 | ||
| 1343 | 0 | this.options.snapshot = arguments.length |
| 1344 | ? !! arguments[0] | |
| 1345 | : true | |
| 1346 | ||
| 1347 | 0 | return this; |
| 1348 | } | |
| 1349 | ||
| 1350 | /** | |
| 1351 | * Sets query hints. | |
| 1352 | * | |
| 1353 | * ####Example | |
| 1354 | * | |
| 1355 | * query.hint({ indexA: 1, indexB: -1}) | |
| 1356 | * | |
| 1357 | * ####Note | |
| 1358 | * | |
| 1359 | * Cannot be used with `distinct()` | |
| 1360 | * | |
| 1361 | * @param {Object} val a hint object | |
| 1362 | * @return {Query} this | |
| 1363 | * @see mongodb http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24hint | |
| 1364 | * @api public | |
| 1365 | */ | |
| 1366 | ||
| 1367 | 1 | Query.prototype.hint = function () { |
| 1368 | 0 | if (0 === arguments.length) return this; |
| 1369 | ||
| 1370 | 0 | this._validate('hint'); |
| 1371 | ||
| 1372 | 0 | var arg = arguments[0]; |
| 1373 | 0 | if (utils.isObject(arg)) { |
| 1374 | 0 | var hint = this.options.hint || (this.options.hint = {}); |
| 1375 | ||
| 1376 | // must keep object keys in order so don't use Object.keys() | |
| 1377 | 0 | for (var k in arg) { |
| 1378 | 0 | hint[k] = arg[k]; |
| 1379 | } | |
| 1380 | ||
| 1381 | 0 | return this; |
| 1382 | } | |
| 1383 | ||
| 1384 | 0 | throw new TypeError('Invalid hint. ' + arg); |
| 1385 | } | |
| 1386 | ||
| 1387 | /** | |
| 1388 | * Sets the slaveOk option. _Deprecated_ in MongoDB 2.2 in favor of read preferences. | |
| 1389 | * | |
| 1390 | * ####Example: | |
| 1391 | * | |
| 1392 | * query.slaveOk() // true | |
| 1393 | * query.slaveOk(true) | |
| 1394 | * query.slaveOk(false) | |
| 1395 | * | |
| 1396 | * @deprecated use read() preferences instead if on mongodb >= 2.2 | |
| 1397 | * @param {Boolean} v defaults to true | |
| 1398 | * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference | |
| 1399 | * @see read() | |
| 1400 | * @return {Query} this | |
| 1401 | * @api public | |
| 1402 | */ | |
| 1403 | ||
| 1404 | 1 | Query.prototype.slaveOk = function (v) { |
| 1405 | 0 | this.options.slaveOk = arguments.length ? !!v : true; |
| 1406 | 0 | return this; |
| 1407 | } | |
| 1408 | ||
| 1409 | /** | |
| 1410 | * Sets the readPreference option for the query. | |
| 1411 | * | |
| 1412 | * ####Example: | |
| 1413 | * | |
| 1414 | * new Query().read('primary') | |
| 1415 | * new Query().read('p') // same as primary | |
| 1416 | * | |
| 1417 | * new Query().read('primaryPreferred') | |
| 1418 | * new Query().read('pp') // same as primaryPreferred | |
| 1419 | * | |
| 1420 | * new Query().read('secondary') | |
| 1421 | * new Query().read('s') // same as secondary | |
| 1422 | * | |
| 1423 | * new Query().read('secondaryPreferred') | |
| 1424 | * new Query().read('sp') // same as secondaryPreferred | |
| 1425 | * | |
| 1426 | * new Query().read('nearest') | |
| 1427 | * new Query().read('n') // same as nearest | |
| 1428 | * | |
| 1429 | * // you can also use mongodb.ReadPreference class to also specify tags | |
| 1430 | * new Query().read(mongodb.ReadPreference('secondary', [{ dc:'sf', s: 1 },{ dc:'ma', s: 2 }])) | |
| 1431 | * | |
| 1432 | * ####Preferences: | |
| 1433 | * | |
| 1434 | * primary - (default) Read from primary only. Operations will produce an error if primary is unavailable. Cannot be combined with tags. | |
| 1435 | * secondary Read from secondary if available, otherwise error. | |
| 1436 | * primaryPreferred Read from primary if available, otherwise a secondary. | |
| 1437 | * secondaryPreferred Read from a secondary if available, otherwise read from the primary. | |
| 1438 | * nearest All operations read from among the nearest candidates, but unlike other modes, this option will include both the primary and all secondaries in the random selection. | |
| 1439 | * | |
| 1440 | * Aliases | |
| 1441 | * | |
| 1442 | * p primary | |
| 1443 | * pp primaryPreferred | |
| 1444 | * s secondary | |
| 1445 | * sp secondaryPreferred | |
| 1446 | * n nearest | |
| 1447 | * | |
| 1448 | * Read more about how to use read preferences [here](http://docs.mongodb.org/manual/applications/replication/#read-preference) and [here](http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences). | |
| 1449 | * | |
| 1450 | * @param {String|ReadPreference} pref one of the listed preference options or their aliases | |
| 1451 | * @see mongodb http://docs.mongodb.org/manual/applications/replication/#read-preference | |
| 1452 | * @see driver http://mongodb.github.com/node-mongodb-native/driver-articles/anintroductionto1_1and2_2.html#read-preferences | |
| 1453 | * @return {Query} this | |
| 1454 | * @api public | |
| 1455 | */ | |
| 1456 | ||
| 1457 | 1 | Query.prototype.read = function (pref) { |
| 1458 | 0 | if (arguments.length > 1 && !Query.prototype.read.deprecationWarningIssued) { |
| 1459 | 0 | console.error("Deprecation warning: 'tags' argument is not supported anymore in Query.read() method. Please use mongodb.ReadPreference object instead."); |
| 1460 | 0 | Query.prototype.read.deprecationWarningIssued = true; |
| 1461 | } | |
| 1462 | 0 | this.options.readPreference = utils.readPref(pref); |
| 1463 | 0 | return this; |
| 1464 | } | |
| 1465 | ||
| 1466 | /** | |
| 1467 | * Sets tailable option. | |
| 1468 | * | |
| 1469 | * ####Example | |
| 1470 | * | |
| 1471 | * query.tailable() <== true | |
| 1472 | * query.tailable(true) | |
| 1473 | * query.tailable(false) | |
| 1474 | * | |
| 1475 | * ####Note | |
| 1476 | * | |
| 1477 | * Cannot be used with `distinct()` | |
| 1478 | * | |
| 1479 | * @param {Boolean} v defaults to true | |
| 1480 | * @see mongodb http://www.mongodb.org/display/DOCS/Tailable+Cursors | |
| 1481 | * @api public | |
| 1482 | */ | |
| 1483 | ||
| 1484 | 1 | Query.prototype.tailable = function () { |
| 1485 | 0 | this._validate('tailable'); |
| 1486 | ||
| 1487 | 0 | this.options.tailable = arguments.length |
| 1488 | ? !! arguments[0] | |
| 1489 | : true; | |
| 1490 | ||
| 1491 | 0 | return this; |
| 1492 | } | |
| 1493 | ||
| 1494 | /** | |
| 1495 | * Merges another Query or conditions object into this one. | |
| 1496 | * | |
| 1497 | * When a Query is passed, conditions, field selection and options are merged. | |
| 1498 | * | |
| 1499 | * @param {Query|Object} source | |
| 1500 | * @return {Query} this | |
| 1501 | */ | |
| 1502 | ||
| 1503 | 1 | Query.prototype.merge = function (source) { |
| 1504 | 0 | if (!source) |
| 1505 | 0 | return this; |
| 1506 | ||
| 1507 | 0 | if (!Query.canMerge(source)) |
| 1508 | 0 | throw new TypeError('Invalid argument. Expected instanceof mquery or plain object'); |
| 1509 | ||
| 1510 | 0 | if (source instanceof Query) { |
| 1511 | // if source has a feature, apply it to ourselves | |
| 1512 | ||
| 1513 | 0 | if (source._conditions) { |
| 1514 | 0 | utils.merge(this._conditions, source._conditions); |
| 1515 | } | |
| 1516 | ||
| 1517 | 0 | if (source._fields) { |
| 1518 | 0 | this._fields || (this._fields = {}); |
| 1519 | 0 | utils.merge(this._fields, source._fields); |
| 1520 | } | |
| 1521 | ||
| 1522 | 0 | if (source.options) { |
| 1523 | 0 | this.options || (this.options = {}); |
| 1524 | 0 | utils.merge(this.options, source.options); |
| 1525 | } | |
| 1526 | ||
| 1527 | 0 | if (source._update) { |
| 1528 | 0 | this._update || (this._update = {}); |
| 1529 | 0 | utils.mergeClone(this._update, source._update); |
| 1530 | } | |
| 1531 | ||
| 1532 | 0 | if (source._distinct) { |
| 1533 | 0 | this._distinct = source._distinct; |
| 1534 | } | |
| 1535 | ||
| 1536 | 0 | return this; |
| 1537 | } | |
| 1538 | ||
| 1539 | // plain object | |
| 1540 | 0 | utils.merge(this._conditions, source); |
| 1541 | ||
| 1542 | 0 | return this; |
| 1543 | } | |
| 1544 | ||
| 1545 | /** | |
| 1546 | * Finds documents. | |
| 1547 | * | |
| 1548 | * Passing a `callback` executes the query. | |
| 1549 | * | |
| 1550 | * ####Example | |
| 1551 | * | |
| 1552 | * query.find() | |
| 1553 | * query.find(callback) | |
| 1554 | * query.find({ name: 'Burning Lights' }, callback) | |
| 1555 | * | |
| 1556 | * @param {Object} [criteria] mongodb selector | |
| 1557 | * @param {Function} [callback] | |
| 1558 | * @return {Query} this | |
| 1559 | * @api public | |
| 1560 | */ | |
| 1561 | ||
| 1562 | 1 | Query.prototype.find = function (criteria, callback) { |
| 1563 | 0 | this.op = 'find'; |
| 1564 | ||
| 1565 | 0 | if ('function' === typeof criteria) { |
| 1566 | 0 | callback = criteria; |
| 1567 | 0 | criteria = undefined; |
| 1568 | 0 | } else if (Query.canMerge(criteria)) { |
| 1569 | 0 | this.merge(criteria); |
| 1570 | } | |
| 1571 | ||
| 1572 | 0 | if (!callback) return this; |
| 1573 | ||
| 1574 | 0 | var self = this |
| 1575 | , conds = this._conditions | |
| 1576 | , options = this._optionsForExec() | |
| 1577 | ||
| 1578 | 0 | options.fields = this._fieldsForExec() |
| 1579 | ||
| 1580 | 0 | debug('find', conds, options); |
| 1581 | ||
| 1582 | 0 | this._collection.find(conds, options, utils.tick(callback)); |
| 1583 | 0 | return this; |
| 1584 | } | |
| 1585 | ||
| 1586 | /** | |
| 1587 | * Executes the query as a findOne() operation. | |
| 1588 | * | |
| 1589 | * Passing a `callback` executes the query. | |
| 1590 | * | |
| 1591 | * ####Example | |
| 1592 | * | |
| 1593 | * query.findOne().where('name', /^Burning/); | |
| 1594 | * | |
| 1595 | * query.findOne({ name: /^Burning/ }) | |
| 1596 | * | |
| 1597 | * query.findOne({ name: /^Burning/ }, callback); // executes | |
| 1598 | * | |
| 1599 | * query.findOne(function (err, doc) { | |
| 1600 | * if (err) return handleError(err); | |
| 1601 | * if (doc) { | |
| 1602 | * // doc may be null if no document matched | |
| 1603 | * | |
| 1604 | * } | |
| 1605 | * }); | |
| 1606 | * | |
| 1607 | * @param {Object|Query} [criteria] mongodb selector | |
| 1608 | * @param {Function} [callback] | |
| 1609 | * @return {Query} this | |
| 1610 | * @api public | |
| 1611 | */ | |
| 1612 | ||
| 1613 | 1 | Query.prototype.findOne = function (criteria, callback) { |
| 1614 | 0 | this.op = 'findOne'; |
| 1615 | ||
| 1616 | 0 | if ('function' === typeof criteria) { |
| 1617 | 0 | callback = criteria; |
| 1618 | 0 | criteria = undefined; |
| 1619 | 0 | } else if (Query.canMerge(criteria)) { |
| 1620 | 0 | this.merge(criteria); |
| 1621 | } | |
| 1622 | ||
| 1623 | 0 | if (!callback) return this; |
| 1624 | ||
| 1625 | 0 | var self = this |
| 1626 | , conds = this._conditions | |
| 1627 | , options = this._optionsForExec() | |
| 1628 | ||
| 1629 | 0 | options.fields = this._fieldsForExec(); |
| 1630 | ||
| 1631 | 0 | debug('findOne', conds, options); |
| 1632 | 0 | this._collection.findOne(conds, options, utils.tick(callback)); |
| 1633 | ||
| 1634 | 0 | return this; |
| 1635 | } | |
| 1636 | ||
| 1637 | /** | |
| 1638 | * Exectues the query as a count() operation. | |
| 1639 | * | |
| 1640 | * Passing a `callback` executes the query. | |
| 1641 | * | |
| 1642 | * ####Example | |
| 1643 | * | |
| 1644 | * query.count().where('color', 'black').exec(callback); | |
| 1645 | * | |
| 1646 | * query.count({ color: 'black' }).count(callback) | |
| 1647 | * | |
| 1648 | * query.count({ color: 'black' }, callback) | |
| 1649 | * | |
| 1650 | * query.where('color', 'black').count(function (err, count) { | |
| 1651 | * if (err) return handleError(err); | |
| 1652 | * console.log('there are %d kittens', count); | |
| 1653 | * }) | |
| 1654 | * | |
| 1655 | * @param {Object} [criteria] mongodb selector | |
| 1656 | * @param {Function} [callback] | |
| 1657 | * @return {Query} this | |
| 1658 | * @see mongodb http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Count | |
| 1659 | * @api public | |
| 1660 | */ | |
| 1661 | ||
| 1662 | 1 | Query.prototype.count = function (criteria, callback) { |
| 1663 | 0 | this.op = 'count'; |
| 1664 | 0 | this._validate(); |
| 1665 | ||
| 1666 | 0 | if ('function' === typeof criteria) { |
| 1667 | 0 | callback = criteria; |
| 1668 | 0 | criteria = undefined; |
| 1669 | 0 | } else if (Query.canMerge(criteria)) { |
| 1670 | 0 | this.merge(criteria); |
| 1671 | } | |
| 1672 | ||
| 1673 | 0 | if (!callback) return this; |
| 1674 | ||
| 1675 | 0 | var conds = this._conditions |
| 1676 | , options = this._optionsForExec() | |
| 1677 | ||
| 1678 | 0 | debug('count', conds, options); |
| 1679 | 0 | this._collection.count(conds, options, utils.tick(callback)); |
| 1680 | 0 | return this; |
| 1681 | } | |
| 1682 | ||
| 1683 | /** | |
| 1684 | * Declares or executes a distict() operation. | |
| 1685 | * | |
| 1686 | * Passing a `callback` executes the query. | |
| 1687 | * | |
| 1688 | * ####Example | |
| 1689 | * | |
| 1690 | * distinct(criteria, field, fn) | |
| 1691 | * distinct(criteria, field) | |
| 1692 | * distinct(field, fn) | |
| 1693 | * distinct(field) | |
| 1694 | * distinct(fn) | |
| 1695 | * distinct() | |
| 1696 | * | |
| 1697 | * @param {Object|Query} [criteria] | |
| 1698 | * @param {String} [field] | |
| 1699 | * @param {Function} [callback] | |
| 1700 | * @return {Query} this | |
| 1701 | * @see mongodb http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Distinct | |
| 1702 | * @api public | |
| 1703 | */ | |
| 1704 | ||
| 1705 | 1 | Query.prototype.distinct = function (criteria, field, callback) { |
| 1706 | 0 | this.op = 'distinct'; |
| 1707 | 0 | this._validate(); |
| 1708 | ||
| 1709 | 0 | if (!callback) { |
| 1710 | 0 | switch (typeof field) { |
| 1711 | case 'function': | |
| 1712 | 0 | callback = field; |
| 1713 | 0 | if ('string' == typeof criteria) { |
| 1714 | 0 | field = criteria; |
| 1715 | 0 | criteria = undefined; |
| 1716 | } | |
| 1717 | 0 | break; |
| 1718 | case 'undefined': | |
| 1719 | case 'string': | |
| 1720 | 0 | break; |
| 1721 | default: | |
| 1722 | 0 | throw new TypeError('Invalid `field` argument. Must be string or function') |
| 1723 | 0 | break; |
| 1724 | } | |
| 1725 | ||
| 1726 | 0 | switch (typeof criteria) { |
| 1727 | case 'function': | |
| 1728 | 0 | callback = criteria; |
| 1729 | 0 | criteria = field = undefined; |
| 1730 | 0 | break; |
| 1731 | case 'string': | |
| 1732 | 0 | field = criteria; |
| 1733 | 0 | criteria = undefined; |
| 1734 | 0 | break; |
| 1735 | } | |
| 1736 | } | |
| 1737 | ||
| 1738 | 0 | if ('string' == typeof field) { |
| 1739 | 0 | this._distinct = field; |
| 1740 | } | |
| 1741 | ||
| 1742 | 0 | if (Query.canMerge(criteria)) { |
| 1743 | 0 | this.merge(criteria); |
| 1744 | } | |
| 1745 | ||
| 1746 | 0 | if (!callback) { |
| 1747 | 0 | return this; |
| 1748 | } | |
| 1749 | ||
| 1750 | 0 | if (!this._distinct) { |
| 1751 | 0 | throw new Error('No value for `distinct` has been declared'); |
| 1752 | } | |
| 1753 | ||
| 1754 | 0 | var conds = this._conditions |
| 1755 | , options = this._optionsForExec() | |
| 1756 | ||
| 1757 | 0 | debug('distinct', conds, options); |
| 1758 | 0 | this._collection.distinct(this._distinct, conds, options, utils.tick(callback)); |
| 1759 | ||
| 1760 | 0 | return this; |
| 1761 | } | |
| 1762 | ||
| 1763 | /** | |
| 1764 | * Declare and/or execute this query as an update() operation. | |
| 1765 | * | |
| 1766 | * _All paths passed that are not $atomic operations will become $set ops._ | |
| 1767 | * | |
| 1768 | * ####Example | |
| 1769 | * | |
| 1770 | * mquery({ _id: id }).update({ title: 'words' }, ...) | |
| 1771 | * | |
| 1772 | * becomes | |
| 1773 | * | |
| 1774 | * collection.update({ _id: id }, { $set: { title: 'words' }}, ...) | |
| 1775 | * | |
| 1776 | * ####Note | |
| 1777 | * | |
| 1778 | * Passing an empty object `{}` as the doc will result in a no-op unless the `overwrite` option is passed. Without the `overwrite` option set, the update operation will be ignored and the callback executed without sending the command to MongoDB so as to prevent accidently overwritting documents in the collection. | |
| 1779 | * | |
| 1780 | * ####Note | |
| 1781 | * | |
| 1782 | * The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call update() and then execute it by using the `exec()` method. | |
| 1783 | * | |
| 1784 | * var q = mquery(collection).where({ _id: id }); | |
| 1785 | * q.update({ $set: { name: 'bob' }}).update(); // not executed | |
| 1786 | * | |
| 1787 | * var q = mquery(collection).where({ _id: id }); | |
| 1788 | * q.update({ $set: { name: 'bob' }}).exec(); // executed as unsafe | |
| 1789 | * | |
| 1790 | * // keys that are not $atomic ops become $set. | |
| 1791 | * // this executes the same command as the previous example. | |
| 1792 | * q.update({ name: 'bob' }).where({ _id: id }).exec(); | |
| 1793 | * | |
| 1794 | * var q = mquery(collection).update(); // not executed | |
| 1795 | * | |
| 1796 | * // overwriting with empty docs | |
| 1797 | * var q.where({ _id: id }).setOptions({ overwrite: true }) | |
| 1798 | * q.update({ }, callback); // executes | |
| 1799 | * | |
| 1800 | * // multi update with overwrite to empty doc | |
| 1801 | * var q = mquery(collection).where({ _id: id }); | |
| 1802 | * q.setOptions({ multi: true, overwrite: true }) | |
| 1803 | * q.update({ }); | |
| 1804 | * q.update(callback); // executed | |
| 1805 | * | |
| 1806 | * // multi updates | |
| 1807 | * mquery() | |
| 1808 | * .collection(coll) | |
| 1809 | * .update({ name: /^match/ }, { $set: { arr: [] }}, { multi: true }, callback) | |
| 1810 | * // more multi updates | |
| 1811 | * mquery({ }) | |
| 1812 | * .collection(coll) | |
| 1813 | * .setOptions({ multi: true }) | |
| 1814 | * .update({ $set: { arr: [] }}, callback) | |
| 1815 | * | |
| 1816 | * // single update by default | |
| 1817 | * mquery({ email: 'address@example.com' }) | |
| 1818 | * .collection(coll) | |
| 1819 | * .update({ $inc: { counter: 1 }}, callback) | |
| 1820 | * | |
| 1821 | * // summary | |
| 1822 | * update(criteria, doc, opts, cb) // executes | |
| 1823 | * update(criteria, doc, opts) | |
| 1824 | * update(criteria, doc, cb) // executes | |
| 1825 | * update(criteria, doc) | |
| 1826 | * update(doc, cb) // executes | |
| 1827 | * update(doc) | |
| 1828 | * update(cb) // executes | |
| 1829 | * update(true) // executes (unsafe write) | |
| 1830 | * update() | |
| 1831 | * | |
| 1832 | * @param {Object} [criteria] | |
| 1833 | * @param {Object} [doc] the update command | |
| 1834 | * @param {Object} [options] | |
| 1835 | * @param {Function} [callback] | |
| 1836 | * @return {Query} this | |
| 1837 | * @api public | |
| 1838 | */ | |
| 1839 | ||
| 1840 | 1 | Query.prototype.update = function update (criteria, doc, options, callback) { |
| 1841 | 0 | this.op = 'update'; |
| 1842 | 0 | var force; |
| 1843 | ||
| 1844 | 0 | switch (arguments.length) { |
| 1845 | case 3: | |
| 1846 | 0 | if ('function' == typeof options) { |
| 1847 | 0 | callback = options; |
| 1848 | 0 | options = undefined; |
| 1849 | } | |
| 1850 | 0 | break; |
| 1851 | case 2: | |
| 1852 | 0 | if ('function' == typeof doc) { |
| 1853 | 0 | callback = doc; |
| 1854 | 0 | doc = criteria; |
| 1855 | 0 | criteria = undefined; |
| 1856 | } | |
| 1857 | 0 | break; |
| 1858 | case 1: | |
| 1859 | 0 | switch (typeof criteria) { |
| 1860 | case 'function': | |
| 1861 | 0 | callback = criteria; |
| 1862 | 0 | criteria = options = doc = undefined; |
| 1863 | 0 | break; |
| 1864 | case 'boolean': | |
| 1865 | // execution with no callback (unsafe write) | |
| 1866 | 0 | force = criteria; |
| 1867 | 0 | criteria = undefined; |
| 1868 | 0 | break; |
| 1869 | default: | |
| 1870 | 0 | doc = criteria; |
| 1871 | 0 | criteria = options = undefined; |
| 1872 | 0 | break; |
| 1873 | } | |
| 1874 | } | |
| 1875 | ||
| 1876 | 0 | if (Query.canMerge(criteria)) { |
| 1877 | 0 | this.merge(criteria); |
| 1878 | } | |
| 1879 | ||
| 1880 | 0 | if (doc) { |
| 1881 | 0 | this._mergeUpdate(doc); |
| 1882 | } | |
| 1883 | ||
| 1884 | 0 | if (utils.isObject(options)) { |
| 1885 | // { overwrite: true } | |
| 1886 | 0 | this.setOptions(options); |
| 1887 | } | |
| 1888 | ||
| 1889 | // we are done if we don't have callback and they are | |
| 1890 | // not forcing an unsafe write. | |
| 1891 | 0 | if (!(force || callback)) |
| 1892 | 0 | return this; |
| 1893 | ||
| 1894 | 0 | if (!this._update || |
| 1895 | !this.options.overwrite && 0 === utils.keys(this._update).length) { | |
| 1896 | 0 | callback && utils.soon(callback.bind(null, null, 0)); |
| 1897 | 0 | return this; |
| 1898 | } | |
| 1899 | ||
| 1900 | 0 | options = this._optionsForExec(); |
| 1901 | 0 | if (!callback) options.safe = false; |
| 1902 | ||
| 1903 | 0 | var criteria = this._conditions; |
| 1904 | 0 | doc = this._updateForExec(); |
| 1905 | ||
| 1906 | 0 | debug('update', criteria, doc, options); |
| 1907 | 0 | this._collection.update(criteria, doc, options, utils.tick(callback)); |
| 1908 | ||
| 1909 | 0 | return this; |
| 1910 | } | |
| 1911 | ||
| 1912 | /** | |
| 1913 | * Declare and/or execute this query as a remove() operation. | |
| 1914 | * | |
| 1915 | * ####Example | |
| 1916 | * | |
| 1917 | * mquery(collection).remove({ artist: 'Anne Murray' }, callback) | |
| 1918 | * | |
| 1919 | * ####Note | |
| 1920 | * | |
| 1921 | * The operation is only executed when a callback is passed. To force execution without a callback (which would be an unsafe write), we must first call remove() and then execute it by using the `exec()` method. | |
| 1922 | * | |
| 1923 | * // not executed | |
| 1924 | * var query = mquery(collection).remove({ name: 'Anne Murray' }) | |
| 1925 | * | |
| 1926 | * // executed | |
| 1927 | * mquery(collection).remove({ name: 'Anne Murray' }, callback) | |
| 1928 | * mquery(collection).remove({ name: 'Anne Murray' }).remove(callback) | |
| 1929 | * | |
| 1930 | * // executed without a callback (unsafe write) | |
| 1931 | * query.exec() | |
| 1932 | * | |
| 1933 | * // summary | |
| 1934 | * query.remove(conds, fn); // executes | |
| 1935 | * query.remove(conds) | |
| 1936 | * query.remove(fn) // executes | |
| 1937 | * query.remove() | |
| 1938 | * | |
| 1939 | * @param {Object|Query} [criteria] mongodb selector | |
| 1940 | * @param {Function} [callback] | |
| 1941 | * @return {Query} this | |
| 1942 | * @api public | |
| 1943 | */ | |
| 1944 | ||
| 1945 | 1 | Query.prototype.remove = function (criteria, callback) { |
| 1946 | 0 | this.op = 'remove'; |
| 1947 | 0 | var force; |
| 1948 | ||
| 1949 | 0 | if ('function' === typeof criteria) { |
| 1950 | 0 | callback = criteria; |
| 1951 | 0 | criteria = undefined; |
| 1952 | 0 | } else if (Query.canMerge(criteria)) { |
| 1953 | 0 | this.merge(criteria); |
| 1954 | 0 | } else if (true === criteria) { |
| 1955 | 0 | force = criteria; |
| 1956 | 0 | criteria = undefined; |
| 1957 | } | |
| 1958 | ||
| 1959 | 0 | if (!(force || callback)) |
| 1960 | 0 | return this; |
| 1961 | ||
| 1962 | 0 | var options = this._optionsForExec() |
| 1963 | 0 | if (!callback) options.safe = false; |
| 1964 | ||
| 1965 | 0 | var conds = this._conditions; |
| 1966 | ||
| 1967 | 0 | debug('remove', conds, options); |
| 1968 | 0 | this._collection.remove(conds, options, utils.tick(callback)); |
| 1969 | ||
| 1970 | 0 | return this; |
| 1971 | } | |
| 1972 | ||
| 1973 | /** | |
| 1974 | * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) update command. | |
| 1975 | * | |
| 1976 | * Finds a matching document, updates it according to the `update` arg, passing any `options`, and returns the found document (if any) to the callback. The query executes immediately if `callback` is passed. | |
| 1977 | * | |
| 1978 | * ####Available options | |
| 1979 | * | |
| 1980 | * - `new`: bool - true to return the modified document rather than the original. defaults to true | |
| 1981 | * - `upsert`: bool - creates the object if it doesn't exist. defaults to false. | |
| 1982 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 1983 | * | |
| 1984 | * ####Examples | |
| 1985 | * | |
| 1986 | * query.findOneAndUpdate(conditions, update, options, callback) // executes | |
| 1987 | * query.findOneAndUpdate(conditions, update, options) // returns Query | |
| 1988 | * query.findOneAndUpdate(conditions, update, callback) // executes | |
| 1989 | * query.findOneAndUpdate(conditions, update) // returns Query | |
| 1990 | * query.findOneAndUpdate(update, callback) // returns Query | |
| 1991 | * query.findOneAndUpdate(update) // returns Query | |
| 1992 | * query.findOneAndUpdate(callback) // executes | |
| 1993 | * query.findOneAndUpdate() // returns Query | |
| 1994 | * | |
| 1995 | * @param {Object|Query} [query] | |
| 1996 | * @param {Object} [doc] | |
| 1997 | * @param {Object} [options] | |
| 1998 | * @param {Function} [callback] | |
| 1999 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 2000 | * @return {Query} this | |
| 2001 | * @api public | |
| 2002 | */ | |
| 2003 | ||
| 2004 | 1 | Query.prototype.findOneAndUpdate = function (criteria, doc, options, callback) { |
| 2005 | 0 | this.op = 'findOneAndUpdate'; |
| 2006 | 0 | this._validate(); |
| 2007 | ||
| 2008 | 0 | switch (arguments.length) { |
| 2009 | case 3: | |
| 2010 | 0 | if ('function' == typeof options) { |
| 2011 | 0 | callback = options; |
| 2012 | 0 | options = {}; |
| 2013 | } | |
| 2014 | 0 | break; |
| 2015 | case 2: | |
| 2016 | 0 | if ('function' == typeof doc) { |
| 2017 | 0 | callback = doc; |
| 2018 | 0 | doc = criteria; |
| 2019 | 0 | criteria = undefined; |
| 2020 | } | |
| 2021 | 0 | options = undefined; |
| 2022 | 0 | break; |
| 2023 | case 1: | |
| 2024 | 0 | if ('function' == typeof criteria) { |
| 2025 | 0 | callback = criteria; |
| 2026 | 0 | criteria = options = doc = undefined; |
| 2027 | } else { | |
| 2028 | 0 | doc = criteria; |
| 2029 | 0 | criteria = options = undefined; |
| 2030 | } | |
| 2031 | } | |
| 2032 | ||
| 2033 | 0 | if (Query.canMerge(criteria)) { |
| 2034 | 0 | this.merge(criteria); |
| 2035 | } | |
| 2036 | ||
| 2037 | // apply doc | |
| 2038 | 0 | if (doc) { |
| 2039 | 0 | this._mergeUpdate(doc); |
| 2040 | } | |
| 2041 | ||
| 2042 | 0 | options && this.setOptions(options); |
| 2043 | ||
| 2044 | 0 | if (!callback) return this; |
| 2045 | 0 | return this._findAndModify('update', callback); |
| 2046 | } | |
| 2047 | ||
| 2048 | /** | |
| 2049 | * Issues a mongodb [findAndModify](http://www.mongodb.org/display/DOCS/findAndModify+Command) remove command. | |
| 2050 | * | |
| 2051 | * Finds a matching document, removes it, passing the found document (if any) to the callback. Executes immediately if `callback` is passed. | |
| 2052 | * | |
| 2053 | * ####Available options | |
| 2054 | * | |
| 2055 | * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update | |
| 2056 | * | |
| 2057 | * ####Examples | |
| 2058 | * | |
| 2059 | * A.where().findOneAndRemove(conditions, options, callback) // executes | |
| 2060 | * A.where().findOneAndRemove(conditions, options) // return Query | |
| 2061 | * A.where().findOneAndRemove(conditions, callback) // executes | |
| 2062 | * A.where().findOneAndRemove(conditions) // returns Query | |
| 2063 | * A.where().findOneAndRemove(callback) // executes | |
| 2064 | * A.where().findOneAndRemove() // returns Query | |
| 2065 | * | |
| 2066 | * @param {Object} [conditions] | |
| 2067 | * @param {Object} [options] | |
| 2068 | * @param {Function} [callback] | |
| 2069 | * @return {Query} this | |
| 2070 | * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command | |
| 2071 | * @api public | |
| 2072 | */ | |
| 2073 | ||
| 2074 | 1 | Query.prototype.findOneAndRemove = function (conditions, options, callback) { |
| 2075 | 0 | this.op = 'findOneAndRemove'; |
| 2076 | 0 | this._validate(); |
| 2077 | ||
| 2078 | 0 | if ('function' == typeof options) { |
| 2079 | 0 | callback = options; |
| 2080 | 0 | options = undefined; |
| 2081 | 0 | } else if ('function' == typeof conditions) { |
| 2082 | 0 | callback = conditions; |
| 2083 | 0 | conditions = undefined; |
| 2084 | } | |
| 2085 | ||
| 2086 | // apply conditions | |
| 2087 | 0 | if (Query.canMerge(conditions)) { |
| 2088 | 0 | this.merge(conditions); |
| 2089 | } | |
| 2090 | ||
| 2091 | // apply options | |
| 2092 | 0 | options && this.setOptions(options); |
| 2093 | ||
| 2094 | 0 | if (!callback) return this; |
| 2095 | ||
| 2096 | 0 | return this._findAndModify('remove', callback); |
| 2097 | } | |
| 2098 | ||
| 2099 | /** | |
| 2100 | * _findAndModify | |
| 2101 | * | |
| 2102 | * @param {String} type - either "remove" or "update" | |
| 2103 | * @param {Function} callback | |
| 2104 | * @api private | |
| 2105 | */ | |
| 2106 | ||
| 2107 | 1 | Query.prototype._findAndModify = function (type, callback) { |
| 2108 | 0 | assert.equal('function', typeof callback); |
| 2109 | ||
| 2110 | 0 | var opts = this._optionsForExec() |
| 2111 | , self = this | |
| 2112 | , fields | |
| 2113 | , sort | |
| 2114 | , doc | |
| 2115 | ||
| 2116 | 0 | if ('remove' == type) { |
| 2117 | 0 | opts.remove = true; |
| 2118 | } else { | |
| 2119 | 0 | if (!('new' in opts)) opts.new = true; |
| 2120 | 0 | if (!('upsert' in opts)) opts.upsert = false; |
| 2121 | ||
| 2122 | 0 | doc = this._updateForExec() |
| 2123 | 0 | if (!doc) { |
| 2124 | 0 | if (opts.upsert) { |
| 2125 | // still need to do the upsert to empty doc | |
| 2126 | 0 | doc = { $set: {} }; |
| 2127 | } else { | |
| 2128 | 0 | return this.findOne(callback); |
| 2129 | } | |
| 2130 | } | |
| 2131 | } | |
| 2132 | ||
| 2133 | 0 | var fields = this._fieldsForExec(); |
| 2134 | 0 | if (fields) { |
| 2135 | 0 | opts.fields = fields; |
| 2136 | } | |
| 2137 | ||
| 2138 | 0 | var conds = this._conditions; |
| 2139 | ||
| 2140 | 0 | debug('findAndModify', conds, doc, opts); |
| 2141 | ||
| 2142 | 0 | this._collection |
| 2143 | .findAndModify(conds, doc, opts, utils.tick(callback)); | |
| 2144 | ||
| 2145 | 0 | return this; |
| 2146 | } | |
| 2147 | ||
| 2148 | /** | |
| 2149 | * Executes the query | |
| 2150 | * | |
| 2151 | * ####Examples | |
| 2152 | * | |
| 2153 | * query.exec(); | |
| 2154 | * query.exec(callback); | |
| 2155 | * query.exec('update'); | |
| 2156 | * query.exec('find', callback); | |
| 2157 | * | |
| 2158 | * @param {String|Function} [operation] | |
| 2159 | * @param {Function} [callback] | |
| 2160 | * @return {Promise} | |
| 2161 | * @api public | |
| 2162 | */ | |
| 2163 | ||
| 2164 | 1 | Query.prototype.exec = function exec (op, callback) { |
| 2165 | 0 | switch (typeof op) { |
| 2166 | case 'function': | |
| 2167 | 0 | callback = op; |
| 2168 | 0 | op = null; |
| 2169 | 0 | break; |
| 2170 | case 'string': | |
| 2171 | 0 | this.op = op; |
| 2172 | 0 | break; |
| 2173 | } | |
| 2174 | ||
| 2175 | 0 | assert.ok(this.op, "Missing query type: (find, update, etc)"); |
| 2176 | ||
| 2177 | 0 | if ('update' == this.op || 'remove' == this.op) { |
| 2178 | 0 | callback || (callback = true); |
| 2179 | } | |
| 2180 | ||
| 2181 | 0 | this[this.op](callback); |
| 2182 | } | |
| 2183 | ||
| 2184 | /** | |
| 2185 | * Determines if field selection has been made. | |
| 2186 | * | |
| 2187 | * @return {Boolean} | |
| 2188 | * @api public | |
| 2189 | */ | |
| 2190 | ||
| 2191 | 1 | Query.prototype.selected = function selected () { |
| 2192 | 0 | return !! (this._fields && Object.keys(this._fields).length > 0); |
| 2193 | } | |
| 2194 | ||
| 2195 | /** | |
| 2196 | * Determines if inclusive field selection has been made. | |
| 2197 | * | |
| 2198 | * query.selectedInclusively() // false | |
| 2199 | * query.select('name') | |
| 2200 | * query.selectedInclusively() // true | |
| 2201 | * query.selectedExlusively() // false | |
| 2202 | * | |
| 2203 | * @returns {Boolean} | |
| 2204 | */ | |
| 2205 | ||
| 2206 | 1 | Query.prototype.selectedInclusively = function selectedInclusively () { |
| 2207 | 0 | if (!this._fields) return false; |
| 2208 | ||
| 2209 | 0 | var keys = Object.keys(this._fields); |
| 2210 | 0 | if (0 === keys.length) return false; |
| 2211 | ||
| 2212 | 0 | for (var i = 0; i < keys.length; ++i) { |
| 2213 | 0 | var key = keys[i]; |
| 2214 | 0 | if (0 === this._fields[key]) return false; |
| 2215 | } | |
| 2216 | ||
| 2217 | 0 | return true; |
| 2218 | } | |
| 2219 | ||
| 2220 | /** | |
| 2221 | * Determines if exclusive field selection has been made. | |
| 2222 | * | |
| 2223 | * query.selectedExlusively() // false | |
| 2224 | * query.select('-name') | |
| 2225 | * query.selectedExlusively() // true | |
| 2226 | * query.selectedInclusively() // false | |
| 2227 | * | |
| 2228 | * @returns {Boolean} | |
| 2229 | */ | |
| 2230 | ||
| 2231 | 1 | Query.prototype.selectedExclusively = function selectedExclusively () { |
| 2232 | 0 | if (!this._fields) return false; |
| 2233 | ||
| 2234 | 0 | var keys = Object.keys(this._fields); |
| 2235 | 0 | if (0 === keys.length) return false; |
| 2236 | ||
| 2237 | 0 | for (var i = 0; i < keys.length; ++i) { |
| 2238 | 0 | var key = keys[i]; |
| 2239 | 0 | if (0 === this._fields[key]) return true; |
| 2240 | } | |
| 2241 | ||
| 2242 | 0 | return false; |
| 2243 | } | |
| 2244 | ||
| 2245 | /** | |
| 2246 | * Merges `doc` with the current update object. | |
| 2247 | * | |
| 2248 | * @param {Object} doc | |
| 2249 | */ | |
| 2250 | ||
| 2251 | 1 | Query.prototype._mergeUpdate = function (doc) { |
| 2252 | 0 | if (!this._update) this._update = {}; |
| 2253 | 0 | if (doc instanceof Query) { |
| 2254 | 0 | if (doc._update) { |
| 2255 | 0 | utils.mergeClone(this._update, doc._update); |
| 2256 | } | |
| 2257 | } else { | |
| 2258 | 0 | utils.mergeClone(this._update, doc); |
| 2259 | } | |
| 2260 | } | |
| 2261 | ||
| 2262 | /** | |
| 2263 | * Returns default options. | |
| 2264 | * | |
| 2265 | * @return {Object} | |
| 2266 | * @api private | |
| 2267 | */ | |
| 2268 | ||
| 2269 | 1 | Query.prototype._optionsForExec = function () { |
| 2270 | 0 | var options = utils.clone(this.options, { retainKeyOrder: true }); |
| 2271 | 0 | return options; |
| 2272 | } | |
| 2273 | ||
| 2274 | /** | |
| 2275 | * Returns fields selection for this query. | |
| 2276 | * | |
| 2277 | * @return {Object} | |
| 2278 | * @api private | |
| 2279 | */ | |
| 2280 | ||
| 2281 | 1 | Query.prototype._fieldsForExec = function () { |
| 2282 | 0 | return utils.clone(this._fields); |
| 2283 | } | |
| 2284 | ||
| 2285 | /** | |
| 2286 | * Return an update document with corrected $set operations. | |
| 2287 | * | |
| 2288 | * @api private | |
| 2289 | */ | |
| 2290 | ||
| 2291 | 1 | Query.prototype._updateForExec = function () { |
| 2292 | 0 | var update = utils.clone(this._update, { retainKeyOrder: true }) |
| 2293 | , ops = utils.keys(update) | |
| 2294 | , i = ops.length | |
| 2295 | , ret = {} | |
| 2296 | , hasKeys | |
| 2297 | , val | |
| 2298 | ||
| 2299 | 0 | while (i--) { |
| 2300 | 0 | var op = ops[i]; |
| 2301 | ||
| 2302 | 0 | if (this.options.overwrite) { |
| 2303 | 0 | ret[op] = update[op]; |
| 2304 | 0 | continue; |
| 2305 | } | |
| 2306 | ||
| 2307 | 0 | if ('$' !== op[0]) { |
| 2308 | // fix up $set sugar | |
| 2309 | 0 | if (!ret.$set) { |
| 2310 | 0 | if (update.$set) { |
| 2311 | 0 | ret.$set = update.$set; |
| 2312 | } else { | |
| 2313 | 0 | ret.$set = {}; |
| 2314 | } | |
| 2315 | } | |
| 2316 | 0 | ret.$set[op] = update[op]; |
| 2317 | 0 | ops.splice(i, 1); |
| 2318 | 0 | if (!~ops.indexOf('$set')) ops.push('$set'); |
| 2319 | 0 | } else if ('$set' === op) { |
| 2320 | 0 | if (!ret.$set) { |
| 2321 | 0 | ret[op] = update[op]; |
| 2322 | } | |
| 2323 | } else { | |
| 2324 | 0 | ret[op] = update[op]; |
| 2325 | } | |
| 2326 | } | |
| 2327 | ||
| 2328 | 0 | return ret; |
| 2329 | } | |
| 2330 | ||
| 2331 | /** | |
| 2332 | * Make sure _path is set. | |
| 2333 | * | |
| 2334 | * @parmam {String} method | |
| 2335 | */ | |
| 2336 | ||
| 2337 | 1 | Query.prototype._ensurePath = function (method) { |
| 2338 | 0 | if (!this._path) { |
| 2339 | 0 | var msg = method + '() must be used after where() ' |
| 2340 | + 'when called with these arguments' | |
| 2341 | 0 | throw new Error(msg); |
| 2342 | } | |
| 2343 | } | |
| 2344 | ||
| 2345 | /*! | |
| 2346 | * Permissions | |
| 2347 | */ | |
| 2348 | ||
| 2349 | 1 | Query.permissions = require('./permissions'); |
| 2350 | ||
| 2351 | 1 | Query._isPermitted = function (a, b) { |
| 2352 | 0 | var denied = Query.permissions[b]; |
| 2353 | 0 | if (!denied) return true; |
| 2354 | 0 | return true !== denied[a]; |
| 2355 | } | |
| 2356 | ||
| 2357 | 1 | Query.prototype._validate = function (action) { |
| 2358 | 0 | var fail; |
| 2359 | 0 | var validator; |
| 2360 | ||
| 2361 | 0 | if (undefined === action) { |
| 2362 | ||
| 2363 | 0 | validator = Query.permissions[this.op]; |
| 2364 | 0 | if ('function' != typeof validator) return true; |
| 2365 | ||
| 2366 | 0 | fail = validator(this); |
| 2367 | ||
| 2368 | 0 | } else if (!Query._isPermitted(action, this.op)) { |
| 2369 | 0 | fail = action; |
| 2370 | } | |
| 2371 | ||
| 2372 | 0 | if (fail) { |
| 2373 | 0 | throw new Error(fail + ' cannot be used with ' + this.op); |
| 2374 | } | |
| 2375 | } | |
| 2376 | ||
| 2377 | /** | |
| 2378 | * Determines if `conds` can be merged using `mquery().merge()` | |
| 2379 | * | |
| 2380 | * @param {Object} conds | |
| 2381 | * @return {Boolean} | |
| 2382 | */ | |
| 2383 | ||
| 2384 | 1 | Query.canMerge = function (conds) { |
| 2385 | 0 | return conds instanceof Query || utils.isObject(conds); |
| 2386 | } | |
| 2387 | ||
| 2388 | /*! | |
| 2389 | * Exports. | |
| 2390 | */ | |
| 2391 | ||
| 2392 | 1 | Query.utils = utils; |
| 2393 | 1 | Query.env = require('./env') |
| 2394 | 1 | Query.Collection = require('./collection'); |
| 2395 | 1 | Query.BaseCollection = require('./collection/collection'); |
| 2396 | 1 | module.exports = exports = Query; |
| 2397 | ||
| 2398 | // TODO | |
| 2399 | // test utils | |
| 2400 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | 1 | var denied = exports; |
| 4 | ||
| 5 | 1 | denied.distinct = function (self) { |
| 6 | 0 | if (self._fields && Object.keys(self._fields).length > 0) { |
| 7 | 0 | return 'field selection and slice' |
| 8 | } | |
| 9 | ||
| 10 | 0 | var keys = Object.keys(denied.distinct); |
| 11 | 0 | var err; |
| 12 | ||
| 13 | 0 | keys.every(function (option) { |
| 14 | 0 | if (self.options[option]) { |
| 15 | 0 | err = option; |
| 16 | 0 | return false; |
| 17 | } | |
| 18 | 0 | return true; |
| 19 | }); | |
| 20 | ||
| 21 | 0 | return err; |
| 22 | }; | |
| 23 | 1 | denied.distinct.select = |
| 24 | denied.distinct.slice = | |
| 25 | denied.distinct.sort = | |
| 26 | denied.distinct.limit = | |
| 27 | denied.distinct.skip = | |
| 28 | denied.distinct.batchSize = | |
| 29 | denied.distinct.comment = | |
| 30 | denied.distinct.maxScan = | |
| 31 | denied.distinct.snapshot = | |
| 32 | denied.distinct.hint = | |
| 33 | denied.distinct.tailable = true; | |
| 34 | ||
| 35 | ||
| 36 | // aggregation integration | |
| 37 | ||
| 38 | ||
| 39 | 1 | denied.findOneAndUpdate = |
| 40 | denied.findOneAndRemove = function (self) { | |
| 41 | 0 | var keys = Object.keys(denied.findOneAndUpdate); |
| 42 | 0 | var err; |
| 43 | ||
| 44 | 0 | keys.every(function (option) { |
| 45 | 0 | if (self.options[option]) { |
| 46 | 0 | err = option; |
| 47 | 0 | return false; |
| 48 | } | |
| 49 | 0 | return true; |
| 50 | }); | |
| 51 | ||
| 52 | 0 | return err; |
| 53 | } | |
| 54 | 1 | denied.findOneAndUpdate.limit = |
| 55 | denied.findOneAndUpdate.skip = | |
| 56 | denied.findOneAndUpdate.batchSize = | |
| 57 | denied.findOneAndUpdate.maxScan = | |
| 58 | denied.findOneAndUpdate.snapshot = | |
| 59 | denied.findOneAndUpdate.hint = | |
| 60 | denied.findOneAndUpdate.tailable = | |
| 61 | denied.findOneAndUpdate.comment = true; | |
| 62 | ||
| 63 | ||
| 64 | 1 | denied.count = function (self) { |
| 65 | 0 | if (self._fields && Object.keys(self._fields).length > 0) { |
| 66 | 0 | return 'field selection and slice' |
| 67 | } | |
| 68 | ||
| 69 | 0 | var keys = Object.keys(denied.count); |
| 70 | 0 | var err; |
| 71 | ||
| 72 | 0 | keys.every(function (option) { |
| 73 | 0 | if (self.options[option]) { |
| 74 | 0 | err = option; |
| 75 | 0 | return false; |
| 76 | } | |
| 77 | 0 | return true; |
| 78 | }); | |
| 79 | ||
| 80 | 0 | return err; |
| 81 | } | |
| 82 | ||
| 83 | 1 | denied.count.select = |
| 84 | denied.count.slice = | |
| 85 | denied.count.sort = | |
| 86 | denied.count.batchSize = | |
| 87 | denied.count.comment = | |
| 88 | denied.count.maxScan = | |
| 89 | denied.count.snapshot = | |
| 90 | denied.count.hint = | |
| 91 | denied.count.tailable = true; | |
| 92 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | 'use strict'; |
| 2 | ||
| 3 | /*! | |
| 4 | * Module dependencies. | |
| 5 | */ | |
| 6 | ||
| 7 | 1 | var RegExpClone = require('regexp-clone') |
| 8 | ||
| 9 | /** | |
| 10 | * Clones objects | |
| 11 | * | |
| 12 | * @param {Object} obj the object to clone | |
| 13 | * @param {Object} options | |
| 14 | * @return {Object} the cloned object | |
| 15 | * @api private | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | var clone = exports.clone = function clone (obj, options) { |
| 19 | 0 | if (obj === undefined || obj === null) |
| 20 | 0 | return obj; |
| 21 | ||
| 22 | 0 | if (Array.isArray(obj)) |
| 23 | 0 | return exports.cloneArray(obj, options); |
| 24 | ||
| 25 | 0 | if (obj.constructor) { |
| 26 | 0 | if (/ObjectI[dD]$/.test(obj.constructor.name)) { |
| 27 | 0 | return 'function' == typeof obj.clone |
| 28 | ? obj.clone() | |
| 29 | : new obj.constructor(obj.id); | |
| 30 | } | |
| 31 | ||
| 32 | 0 | if ('ReadPreference' === obj._type && obj.isValid && obj.toObject) { |
| 33 | 0 | return 'function' == typeof obj.clone |
| 34 | ? obj.clone() | |
| 35 | : new obj.constructor(obj.mode, clone(obj.tags, options)); | |
| 36 | } | |
| 37 | ||
| 38 | 0 | if ('Binary' == obj._bsontype && obj.buffer && obj.value) { |
| 39 | 0 | return 'function' == typeof obj.clone |
| 40 | ? obj.clone() | |
| 41 | : new obj.constructor(obj.value(true), obj.sub_type); | |
| 42 | } | |
| 43 | ||
| 44 | 0 | if ('Date' === obj.constructor.name || 'Function' === obj.constructor.name) |
| 45 | 0 | return new obj.constructor(+obj); |
| 46 | ||
| 47 | 0 | if ('RegExp' === obj.constructor.name) |
| 48 | 0 | return RegExpClone(obj); |
| 49 | } | |
| 50 | ||
| 51 | 0 | if (isObject(obj)) |
| 52 | 0 | return exports.cloneObject(obj, options); |
| 53 | ||
| 54 | 0 | if (obj.valueOf) |
| 55 | 0 | return obj.valueOf(); |
| 56 | }; | |
| 57 | ||
| 58 | /*! | |
| 59 | * ignore | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | var cloneObject = exports.cloneObject = function cloneObject (obj, options) { |
| 63 | 0 | var retainKeyOrder = options && options.retainKeyOrder |
| 64 | , minimize = options && options.minimize | |
| 65 | , ret = {} | |
| 66 | , hasKeys | |
| 67 | , keys | |
| 68 | , val | |
| 69 | , k | |
| 70 | , i | |
| 71 | ||
| 72 | 0 | if (retainKeyOrder) { |
| 73 | 0 | for (k in obj) { |
| 74 | 0 | val = clone(obj[k], options); |
| 75 | ||
| 76 | 0 | if (!minimize || ('undefined' !== typeof val)) { |
| 77 | 0 | hasKeys || (hasKeys = true); |
| 78 | 0 | ret[k] = val; |
| 79 | } | |
| 80 | } | |
| 81 | } else { | |
| 82 | // faster | |
| 83 | ||
| 84 | 0 | keys = Object.keys(obj); |
| 85 | 0 | i = keys.length; |
| 86 | ||
| 87 | 0 | while (i--) { |
| 88 | 0 | k = keys[i]; |
| 89 | 0 | val = clone(obj[k], options); |
| 90 | ||
| 91 | 0 | if (!minimize || ('undefined' !== typeof val)) { |
| 92 | 0 | if (!hasKeys) hasKeys = true; |
| 93 | 0 | ret[k] = val; |
| 94 | } | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | 0 | return minimize |
| 99 | ? hasKeys && ret | |
| 100 | : ret; | |
| 101 | }; | |
| 102 | ||
| 103 | 1 | var cloneArray = exports.cloneArray = function cloneArray (arr, options) { |
| 104 | 0 | var ret = []; |
| 105 | 0 | for (var i = 0, l = arr.length; i < l; i++) |
| 106 | 0 | ret.push(clone(arr[i], options)); |
| 107 | 0 | return ret; |
| 108 | }; | |
| 109 | ||
| 110 | /** | |
| 111 | * process.nextTick helper. | |
| 112 | * | |
| 113 | * Wraps the given `callback` in a try/catch. If an error is | |
| 114 | * caught it will be thrown on nextTick. | |
| 115 | * | |
| 116 | * node-mongodb-native had a habit of state corruption when | |
| 117 | * an error was immediately thrown from within a collection | |
| 118 | * method (find, update, etc) callback. | |
| 119 | * | |
| 120 | * @param {Function} [callback] | |
| 121 | * @api private | |
| 122 | */ | |
| 123 | ||
| 124 | 1 | var tick = exports.tick = function tick (callback) { |
| 125 | 0 | if ('function' !== typeof callback) return; |
| 126 | 0 | return function () { |
| 127 | // callbacks should always be fired on the next | |
| 128 | // turn of the event loop. A side benefit is | |
| 129 | // errors thrown from executing the callback | |
| 130 | // will not cause drivers state to be corrupted | |
| 131 | // which has historically been a problem. | |
| 132 | 0 | var args = arguments; |
| 133 | 0 | soon(function(){ |
| 134 | 0 | callback.apply(this, args); |
| 135 | }); | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | /** | |
| 140 | * Merges `from` into `to` without overwriting existing properties. | |
| 141 | * | |
| 142 | * @param {Object} to | |
| 143 | * @param {Object} from | |
| 144 | * @api private | |
| 145 | */ | |
| 146 | ||
| 147 | 1 | var merge = exports.merge = function merge (to, from) { |
| 148 | 0 | var keys = Object.keys(from) |
| 149 | , i = keys.length | |
| 150 | , key | |
| 151 | ||
| 152 | 0 | while (i--) { |
| 153 | 0 | key = keys[i]; |
| 154 | 0 | if ('undefined' === typeof to[key]) { |
| 155 | 0 | to[key] = from[key]; |
| 156 | } else { | |
| 157 | 0 | if (exports.isObject(from[key])) { |
| 158 | 0 | merge(to[key], from[key]); |
| 159 | } else { | |
| 160 | 0 | to[key] = from[key]; |
| 161 | } | |
| 162 | } | |
| 163 | } | |
| 164 | } | |
| 165 | ||
| 166 | /** | |
| 167 | * Same as merge but clones the assigned values. | |
| 168 | * | |
| 169 | * @param {Object} to | |
| 170 | * @param {Object} from | |
| 171 | * @api private | |
| 172 | */ | |
| 173 | ||
| 174 | 1 | var mergeClone = exports.mergeClone = function mergeClone (to, from) { |
| 175 | 0 | var keys = Object.keys(from) |
| 176 | , i = keys.length | |
| 177 | , key | |
| 178 | ||
| 179 | 0 | while (i--) { |
| 180 | 0 | key = keys[i]; |
| 181 | 0 | if ('undefined' === typeof to[key]) { |
| 182 | // make sure to retain key order here because of a bug handling the $each | |
| 183 | // operator in mongodb 2.4.4 | |
| 184 | 0 | to[key] = clone(from[key], { retainKeyOrder : 1}); |
| 185 | } else { | |
| 186 | 0 | if (exports.isObject(from[key])) { |
| 187 | 0 | mergeClone(to[key], from[key]); |
| 188 | } else { | |
| 189 | // make sure to retain key order here because of a bug handling the | |
| 190 | // $each operator in mongodb 2.4.4 | |
| 191 | 0 | to[key] = clone(from[key], { retainKeyOrder : 1}); |
| 192 | } | |
| 193 | } | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | /** | |
| 198 | * Read pref helper (mongo 2.2 drivers support this) | |
| 199 | * | |
| 200 | * Allows using aliases instead of full preference names: | |
| 201 | * | |
| 202 | * p primary | |
| 203 | * pp primaryPreferred | |
| 204 | * s secondary | |
| 205 | * sp secondaryPreferred | |
| 206 | * n nearest | |
| 207 | * | |
| 208 | * @param {String} pref | |
| 209 | */ | |
| 210 | ||
| 211 | 1 | exports.readPref = function readPref (pref) { |
| 212 | 0 | switch (pref) { |
| 213 | case 'p': | |
| 214 | 0 | pref = 'primary'; |
| 215 | 0 | break; |
| 216 | case 'pp': | |
| 217 | 0 | pref = 'primaryPreferred'; |
| 218 | 0 | break; |
| 219 | case 's': | |
| 220 | 0 | pref = 'secondary'; |
| 221 | 0 | break; |
| 222 | case 'sp': | |
| 223 | 0 | pref = 'secondaryPreferred'; |
| 224 | 0 | break; |
| 225 | case 'n': | |
| 226 | 0 | pref = 'nearest'; |
| 227 | 0 | break; |
| 228 | } | |
| 229 | ||
| 230 | 0 | return pref; |
| 231 | } | |
| 232 | ||
| 233 | /** | |
| 234 | * Object.prototype.toString.call helper | |
| 235 | */ | |
| 236 | ||
| 237 | 1 | var _toString = Object.prototype.toString; |
| 238 | 1 | var toString = exports.toString = function (arg) { |
| 239 | 0 | return _toString.call(arg); |
| 240 | } | |
| 241 | ||
| 242 | /** | |
| 243 | * Determines if `arg` is an object. | |
| 244 | * | |
| 245 | * @param {Object|Array|String|Function|RegExp|any} arg | |
| 246 | * @return {Boolean} | |
| 247 | */ | |
| 248 | ||
| 249 | 1 | var isObject = exports.isObject = function (arg) { |
| 250 | 0 | return '[object Object]' == exports.toString(arg); |
| 251 | } | |
| 252 | ||
| 253 | /** | |
| 254 | * Determines if `arg` is an array. | |
| 255 | * | |
| 256 | * @param {Object} | |
| 257 | * @return {Boolean} | |
| 258 | * @see nodejs utils | |
| 259 | */ | |
| 260 | ||
| 261 | 1 | var isArray = exports.isArray = function (arg) { |
| 262 | 0 | return Array.isArray(arg) || |
| 263 | 'object' == typeof arg && '[object Array]' == exports.toString(arg); | |
| 264 | } | |
| 265 | ||
| 266 | /** | |
| 267 | * Object.keys helper | |
| 268 | */ | |
| 269 | ||
| 270 | 1 | exports.keys = Object.keys || function (obj) { |
| 271 | 0 | var keys = []; |
| 272 | 0 | for (var k in obj) if (obj.hasOwnProperty(k)) { |
| 273 | 0 | keys.push(k); |
| 274 | } | |
| 275 | 0 | return keys; |
| 276 | } | |
| 277 | ||
| 278 | /** | |
| 279 | * Basic Object.create polyfill. | |
| 280 | * Only one argument is supported. | |
| 281 | * | |
| 282 | * Based on https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create | |
| 283 | */ | |
| 284 | ||
| 285 | 1 | exports.create = 'function' == typeof Object.create |
| 286 | ? Object.create | |
| 287 | : create; | |
| 288 | ||
| 289 | 1 | function create (proto) { |
| 290 | 0 | if (arguments.length > 1) { |
| 291 | 0 | throw new Error("Adding properties is not supported") |
| 292 | } | |
| 293 | ||
| 294 | 0 | function F () {} |
| 295 | 0 | F.prototype = proto; |
| 296 | 0 | return new F; |
| 297 | } | |
| 298 | ||
| 299 | /** | |
| 300 | * inheritance | |
| 301 | */ | |
| 302 | ||
| 303 | 1 | exports.inherits = function (ctor, superCtor) { |
| 304 | 1 | ctor.prototype = exports.create(superCtor.prototype); |
| 305 | 1 | ctor.prototype.constructor = ctor; |
| 306 | } | |
| 307 | ||
| 308 | /** | |
| 309 | * nextTick helper | |
| 310 | * compat with node 0.10 which behaves differently than previous versions | |
| 311 | */ | |
| 312 | ||
| 313 | 1 | var soon = exports.soon = 'function' == typeof setImmediate |
| 314 | ? setImmediate | |
| 315 | : process.nextTick; | |
| 316 | ||
| 317 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var tty = require('tty'); |
| 6 | ||
| 7 | /** | |
| 8 | * Expose `debug()` as the module. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = debug; |
| 12 | ||
| 13 | /** | |
| 14 | * Enabled debuggers. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | var names = [] |
| 18 | , skips = []; | |
| 19 | ||
| 20 | 1 | (process.env.DEBUG || '') |
| 21 | .split(/[\s,]+/) | |
| 22 | .forEach(function(name){ | |
| 23 | 1 | name = name.replace('*', '.*?'); |
| 24 | 1 | if (name[0] === '-') { |
| 25 | 0 | skips.push(new RegExp('^' + name.substr(1) + '$')); |
| 26 | } else { | |
| 27 | 1 | names.push(new RegExp('^' + name + '$')); |
| 28 | } | |
| 29 | }); | |
| 30 | ||
| 31 | /** | |
| 32 | * Colors. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | var colors = [6, 2, 3, 4, 5, 1]; |
| 36 | ||
| 37 | /** | |
| 38 | * Previous debug() call. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | var prev = {}; |
| 42 | ||
| 43 | /** | |
| 44 | * Previously assigned color. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | var prevColor = 0; |
| 48 | ||
| 49 | /** | |
| 50 | * Is stdout a TTY? Colored output is disabled when `true`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | var isatty = tty.isatty(2); |
| 54 | ||
| 55 | /** | |
| 56 | * Select a color. | |
| 57 | * | |
| 58 | * @return {Number} | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | function color() { |
| 63 | 0 | return colors[prevColor++ % colors.length]; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Humanize the given `ms`. | |
| 68 | * | |
| 69 | * @param {Number} m | |
| 70 | * @return {String} | |
| 71 | * @api private | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | function humanize(ms) { |
| 75 | 0 | var sec = 1000 |
| 76 | , min = 60 * 1000 | |
| 77 | , hour = 60 * min; | |
| 78 | ||
| 79 | 0 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; |
| 80 | 0 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; |
| 81 | 0 | if (ms >= sec) return (ms / sec | 0) + 's'; |
| 82 | 0 | return ms + 'ms'; |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Create a debugger with the given `name`. | |
| 87 | * | |
| 88 | * @param {String} name | |
| 89 | * @return {Type} | |
| 90 | * @api public | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function debug(name) { |
| 94 | 1 | function disabled(){} |
| 95 | 1 | disabled.enabled = false; |
| 96 | ||
| 97 | 1 | var match = skips.some(function(re){ |
| 98 | 0 | return re.test(name); |
| 99 | }); | |
| 100 | ||
| 101 | 1 | if (match) return disabled; |
| 102 | ||
| 103 | 1 | match = names.some(function(re){ |
| 104 | 1 | return re.test(name); |
| 105 | }); | |
| 106 | ||
| 107 | 2 | if (!match) return disabled; |
| 108 | 0 | var c = color(); |
| 109 | ||
| 110 | 0 | function colored(fmt) { |
| 111 | 0 | fmt = coerce(fmt); |
| 112 | ||
| 113 | 0 | var curr = new Date; |
| 114 | 0 | var ms = curr - (prev[name] || curr); |
| 115 | 0 | prev[name] = curr; |
| 116 | ||
| 117 | 0 | fmt = ' \u001b[9' + c + 'm' + name + ' ' |
| 118 | + '\u001b[3' + c + 'm\u001b[90m' | |
| 119 | + fmt + '\u001b[3' + c + 'm' | |
| 120 | + ' +' + humanize(ms) + '\u001b[0m'; | |
| 121 | ||
| 122 | 0 | console.error.apply(this, arguments); |
| 123 | } | |
| 124 | ||
| 125 | 0 | function plain(fmt) { |
| 126 | 0 | fmt = coerce(fmt); |
| 127 | ||
| 128 | 0 | fmt = new Date().toUTCString() |
| 129 | + ' ' + name + ' ' + fmt; | |
| 130 | 0 | console.error.apply(this, arguments); |
| 131 | } | |
| 132 | ||
| 133 | 0 | colored.enabled = plain.enabled = true; |
| 134 | ||
| 135 | 0 | return isatty || process.env.DEBUG_COLORS |
| 136 | ? colored | |
| 137 | : plain; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Coerce `val`. | |
| 142 | */ | |
| 143 | ||
| 144 | 1 | function coerce(val) { |
| 145 | 0 | if (val instanceof Error) return val.stack || val.message; |
| 146 | 0 | return val; |
| 147 | } | |
| 148 |
| Line | Hits | Source |
|---|---|---|
| 1 | // muri | |
| 2 | ||
| 3 | /** | |
| 4 | * MongoDB URI parser as described here: | |
| 5 | * http://www.mongodb.org/display/DOCS/Connections | |
| 6 | */ | |
| 7 | ||
| 8 | /** | |
| 9 | * Module dependencies | |
| 10 | */ | |
| 11 | ||
| 12 | 1 | var qs = require('querystring'); |
| 13 | ||
| 14 | /** | |
| 15 | * Defaults | |
| 16 | */ | |
| 17 | ||
| 18 | 1 | const DEFAULT_PORT = 27017; |
| 19 | 1 | const DEFAULT_DB = 'test'; |
| 20 | 1 | const ADMIN_DB = 'admin'; |
| 21 | ||
| 22 | /** | |
| 23 | * Muri | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | module.exports = exports = function muri (str) { |
| 27 | 1 | if (!/^mongodb:\/\//.test(str)) { |
| 28 | 0 | throw new Error('Invalid mongodb uri. Must begin with "mongodb://"' |
| 29 | + '\n Received: ' + str); | |
| 30 | } | |
| 31 | ||
| 32 | 1 | var ret = { |
| 33 | hosts: [] | |
| 34 | , db: DEFAULT_DB | |
| 35 | , options: {} | |
| 36 | } | |
| 37 | ||
| 38 | 1 | var match = /^mongodb:\/\/([^?]+)(\??.*)$/.exec(str); |
| 39 | 1 | if (!match || '/' == match[1]) { |
| 40 | 0 | throw new Error('Invalid mongodb uri. Missing hostname'); |
| 41 | } | |
| 42 | ||
| 43 | 1 | var uris = match[1]; |
| 44 | 1 | var path = match[2]; |
| 45 | 1 | var db; |
| 46 | ||
| 47 | 1 | uris.split(',').forEach(function (uri) { |
| 48 | 1 | var o = parse(uri); |
| 49 | ||
| 50 | 1 | if (o.host) { |
| 51 | 1 | ret.hosts.push({ |
| 52 | host: o.host | |
| 53 | , port: parseInt(o.port, 10) | |
| 54 | }) | |
| 55 | ||
| 56 | 1 | if (!db && o.db) { |
| 57 | 0 | db = o.db; |
| 58 | } | |
| 59 | 0 | } else if (o.ipc) { |
| 60 | 0 | ret.hosts.push({ ipc: o.ipc }); |
| 61 | } | |
| 62 | ||
| 63 | 1 | if (o.auth) { |
| 64 | 0 | ret.auth = { |
| 65 | user: o.auth.user | |
| 66 | , pass: o.auth.pass | |
| 67 | } | |
| 68 | } | |
| 69 | }) | |
| 70 | ||
| 71 | 1 | if (!ret.hosts.length) { |
| 72 | 0 | throw new Error('Invalid mongodb uri. Missing hostname'); |
| 73 | } | |
| 74 | ||
| 75 | 1 | var parts = path.split('?'); |
| 76 | ||
| 77 | 1 | if (!db) { |
| 78 | 1 | if (parts[0]) { |
| 79 | 0 | db = parts[0].replace(/^\//, ''); |
| 80 | } else { | |
| 81 | // deal with ipc formats | |
| 82 | 1 | db = /\/([^\.]+)$/.exec(match[1]); |
| 83 | 1 | if (db && db[1]) { |
| 84 | 0 | db = db[1]; |
| 85 | } | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | 1 | if (db) { |
| 90 | 0 | ret.db = db; |
| 91 | 1 | } else if (ret.auth) { |
| 92 | 0 | ret.db = ADMIN_DB; |
| 93 | } | |
| 94 | ||
| 95 | 1 | if (parts[1]) { |
| 96 | 0 | ret.options = options(parts[1]); |
| 97 | } | |
| 98 | ||
| 99 | 1 | return ret; |
| 100 | } | |
| 101 | ||
| 102 | /** | |
| 103 | * Parse str into key/val pairs casting values appropriately. | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | function options (str) { |
| 107 | 0 | var sep = /;/.test(str) |
| 108 | ? ';' | |
| 109 | : '&'; | |
| 110 | ||
| 111 | 0 | var ret = qs.parse(str, sep); |
| 112 | ||
| 113 | 0 | Object.keys(ret).forEach(function (key) { |
| 114 | 0 | var val = ret[key]; |
| 115 | 0 | if ('readPreferenceTags' == key) { |
| 116 | 0 | val = readPref(val); |
| 117 | 0 | if (val) { |
| 118 | 0 | ret[key] = Array.isArray(val) |
| 119 | ? val | |
| 120 | : [val]; | |
| 121 | } | |
| 122 | } else { | |
| 123 | 0 | ret[key] = format(val); |
| 124 | } | |
| 125 | }); | |
| 126 | ||
| 127 | 0 | return ret; |
| 128 | } | |
| 129 | ||
| 130 | 1 | function format (val) { |
| 131 | 0 | var num; |
| 132 | ||
| 133 | 0 | if ('true' == val) { |
| 134 | 0 | return true; |
| 135 | 0 | } else if ('false' == val) { |
| 136 | 0 | return false; |
| 137 | } else { | |
| 138 | 0 | num = parseInt(val, 10); |
| 139 | 0 | if (!isNaN(num)) { |
| 140 | 0 | return num; |
| 141 | } | |
| 142 | } | |
| 143 | ||
| 144 | 0 | return val; |
| 145 | } | |
| 146 | ||
| 147 | 1 | function readPref (val) { |
| 148 | 0 | var ret; |
| 149 | ||
| 150 | 0 | if (Array.isArray(val)) { |
| 151 | 0 | ret = val.map(readPref).filter(Boolean); |
| 152 | 0 | return ret.length |
| 153 | ? ret | |
| 154 | : undefined | |
| 155 | } | |
| 156 | ||
| 157 | 0 | var pair = val.split(','); |
| 158 | 0 | var hasKeys; |
| 159 | 0 | ret = {}; |
| 160 | ||
| 161 | 0 | pair.forEach(function (kv) { |
| 162 | 0 | kv = (kv || '').trim(); |
| 163 | 0 | if (!kv) return; |
| 164 | 0 | hasKeys = true; |
| 165 | 0 | var split = kv.split(':'); |
| 166 | 0 | ret[split[0]] = format(split[1]); |
| 167 | }); | |
| 168 | ||
| 169 | 0 | return hasKeys && ret; |
| 170 | } | |
| 171 | ||
| 172 | 1 | var ipcRgx = /\.sock/; |
| 173 | ||
| 174 | 1 | function parse (uriString) { |
| 175 | // do not use require('url').parse b/c it can't handle # in username or pwd | |
| 176 | // mongo uris are strange | |
| 177 | ||
| 178 | 1 | var uri = uriString; |
| 179 | 1 | var ret = {}; |
| 180 | 1 | var parts; |
| 181 | 1 | var auth; |
| 182 | 1 | var ipcs; |
| 183 | ||
| 184 | // skip protocol | |
| 185 | 1 | uri = uri.replace(/^mongodb:\/\//, ''); |
| 186 | ||
| 187 | // auth | |
| 188 | 1 | if (/@/.test(uri)) { |
| 189 | 0 | parts = uri.split(/@/); |
| 190 | 0 | auth = parts[0]; |
| 191 | 0 | uri = parts[1]; |
| 192 | ||
| 193 | 0 | parts = auth.split(':'); |
| 194 | 0 | ret.auth = {}; |
| 195 | 0 | ret.auth.user = parts[0]; |
| 196 | 0 | ret.auth.pass = parts[1]; |
| 197 | } | |
| 198 | ||
| 199 | // unix domain sockets | |
| 200 | 1 | if (ipcRgx.test(uri)) { |
| 201 | 0 | ipcs = uri.split(ipcRgx); |
| 202 | 0 | ret.ipc = ipcs[0] + '.sock'; |
| 203 | ||
| 204 | // included a database? | |
| 205 | 0 | if (ipcs[1]) { |
| 206 | // strip leading / from database name | |
| 207 | 0 | ipcs[1] = ipcs[1].replace(/^\//, ''); |
| 208 | ||
| 209 | 0 | if (ipcs[1]) { |
| 210 | 0 | ret.db = ipcs[1]; |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | 0 | return ret; |
| 215 | } | |
| 216 | ||
| 217 | // database name | |
| 218 | 1 | parts = uri.split('/'); |
| 219 | 1 | if (parts[1]) ret.db = parts[1]; |
| 220 | ||
| 221 | // host:port | |
| 222 | 1 | parts = parts[0].split(':'); |
| 223 | 1 | ret.host = parts[0]; |
| 224 | 1 | ret.port = parts[1] || DEFAULT_PORT; |
| 225 | ||
| 226 | 1 | return ret; |
| 227 | } | |
| 228 | ||
| 229 | /** | |
| 230 | * Version | |
| 231 | */ | |
| 232 | ||
| 233 | 1 | module.exports.version = JSON.parse( |
| 234 | require('fs').readFileSync(__dirname + '/../package.json', 'utf8') | |
| 235 | ).version; | |
| 236 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * An Array.prototype.slice.call(arguments) alternative | |
| 4 | * | |
| 5 | * @param {Object} args something with a length | |
| 6 | * @param {Number} slice | |
| 7 | * @param {Number} sliceEnd | |
| 8 | * @api public | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = function (args, slice, sliceEnd) { |
| 12 | 1 | var ret = []; |
| 13 | 1 | var len = args.length; |
| 14 | ||
| 15 | 1 | if (0 === len) return ret; |
| 16 | ||
| 17 | 1 | var start = slice < 0 |
| 18 | ? Math.max(0, slice + len) | |
| 19 | : slice || 0; | |
| 20 | ||
| 21 | 1 | if (sliceEnd !== undefined) { |
| 22 | 0 | len = sliceEnd < 0 |
| 23 | ? sliceEnd + len | |
| 24 | : sliceEnd | |
| 25 | } | |
| 26 | ||
| 27 | 1 | while (len-- > start) { |
| 28 | 4 | ret[len - start] = args[len]; |
| 29 | } | |
| 30 | ||
| 31 | 1 | return ret; |
| 32 | } | |
| 33 | ||
| 34 |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * nconf.js: Top-level include for the nconf module | |
| 3 | * | |
| 4 | * (C) 2011, Nodejitsu Inc. | |
| 5 | * | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var fs = require('fs'), |
| 9 | async = require('async'), | |
| 10 | common = require('./nconf/common'), | |
| 11 | Provider = require('./nconf/provider').Provider, | |
| 12 | nconf = module.exports = new Provider(); | |
| 13 | ||
| 14 | // | |
| 15 | // Expose the version from the package.json | |
| 16 | // | |
| 17 | 1 | nconf.version = require('../package.json').version; |
| 18 | ||
| 19 | // | |
| 20 | // Setup all stores as lazy-loaded getters. | |
| 21 | // | |
| 22 | 1 | fs.readdirSync(__dirname + '/nconf/stores').forEach(function (file) { |
| 23 | 5 | var store = file.replace('.js', ''), |
| 24 | name = common.capitalize(store); | |
| 25 | ||
| 26 | 5 | nconf.__defineGetter__(name, function () { |
| 27 | 0 | return require('./nconf/stores/' + store)[name]; |
| 28 | }); | |
| 29 | }); | |
| 30 | ||
| 31 | // | |
| 32 | // Expose the various components included with nconf | |
| 33 | // | |
| 34 | 1 | nconf.key = common.key; |
| 35 | 1 | nconf.path = common.path; |
| 36 | 1 | nconf.loadFiles = common.loadFiles; |
| 37 | 1 | nconf.loadFilesSync = common.loadFilesSync; |
| 38 | 1 | nconf.formats = require('./nconf/formats'); |
| 39 | 1 | nconf.Provider = Provider; |
| 40 | ||
| 41 |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * utils.js: Utility functions for the nconf module. | |
| 3 | * | |
| 4 | * (C) 2011, Nodejitsu Inc. | |
| 5 | * | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var fs = require('fs'), |
| 9 | async = require('async'), | |
| 10 | formats = require('./formats'), | |
| 11 | Memory = require('./stores/memory').Memory; | |
| 12 | ||
| 13 | 1 | var common = exports; |
| 14 | ||
| 15 | // | |
| 16 | // ### function path (key) | |
| 17 | // #### @key {string} The ':' delimited key to split | |
| 18 | // Returns a fully-qualified path to a nested nconf key. | |
| 19 | // If given null or undefined it should return an empty path. | |
| 20 | // '' should still be respected as a path. | |
| 21 | // | |
| 22 | 1 | common.path = function (key) { |
| 23 | 0 | return key == null ? [] : key.split(':'); |
| 24 | }; | |
| 25 | ||
| 26 | // | |
| 27 | // ### function key (arguments) | |
| 28 | // Returns a `:` joined string from the `arguments`. | |
| 29 | // | |
| 30 | 1 | common.key = function () { |
| 31 | 0 | return Array.prototype.slice.call(arguments).join(':'); |
| 32 | }; | |
| 33 | ||
| 34 | // | |
| 35 | // ### function loadFiles (files, callback) | |
| 36 | // #### @files {Object|Array} List of files (or settings object) to load. | |
| 37 | // #### @callback {function} Continuation to respond to when complete. | |
| 38 | // Loads all the data in the specified `files`. | |
| 39 | // | |
| 40 | 1 | common.loadFiles = function (files, callback) { |
| 41 | 0 | if (!files) { |
| 42 | 0 | return callback(null, {}); |
| 43 | } | |
| 44 | ||
| 45 | 0 | var options = Array.isArray(files) ? { files: files } : files; |
| 46 | ||
| 47 | // | |
| 48 | // Set the default JSON format if not already | |
| 49 | // specified | |
| 50 | // | |
| 51 | 0 | options.format = options.format || formats.json; |
| 52 | ||
| 53 | 0 | function parseFile (file, next) { |
| 54 | 0 | fs.readFile(file, function (err, data) { |
| 55 | 0 | return !err |
| 56 | ? next(null, options.format.parse(data.toString())) | |
| 57 | : next(err); | |
| 58 | }); | |
| 59 | } | |
| 60 | ||
| 61 | 0 | async.map(options.files, parseFile, function (err, objs) { |
| 62 | 0 | return err ? callback(err) : callback(null, common.merge(objs)); |
| 63 | }); | |
| 64 | }; | |
| 65 | ||
| 66 | // | |
| 67 | // ### function loadFilesSync (files) | |
| 68 | // #### @files {Object|Array} List of files (or settings object) to load. | |
| 69 | // Loads all the data in the specified `files` synchronously. | |
| 70 | // | |
| 71 | 1 | common.loadFilesSync = function (files) { |
| 72 | 0 | if (!files) { |
| 73 | 0 | return; |
| 74 | } | |
| 75 | ||
| 76 | // | |
| 77 | // Set the default JSON format if not already | |
| 78 | // specified | |
| 79 | // | |
| 80 | 0 | var options = Array.isArray(files) ? { files: files } : files; |
| 81 | 0 | options.format = options.format || formats.json; |
| 82 | ||
| 83 | 0 | return common.merge(options.files.map(function (file) { |
| 84 | 0 | return options.format.parse(fs.readFileSync(file, 'utf8')); |
| 85 | })); | |
| 86 | }; | |
| 87 | ||
| 88 | // | |
| 89 | // ### function merge (objs) | |
| 90 | // #### @objs {Array} Array of object literals to merge | |
| 91 | // Merges the specified `objs` using a temporary instance | |
| 92 | // of `stores.Memory`. | |
| 93 | // | |
| 94 | 1 | common.merge = function (objs) { |
| 95 | 0 | var store = new Memory(); |
| 96 | ||
| 97 | 0 | objs.forEach(function (obj) { |
| 98 | 0 | Object.keys(obj).forEach(function (key) { |
| 99 | 0 | store.merge(key, obj[key]); |
| 100 | }); | |
| 101 | }); | |
| 102 | ||
| 103 | 0 | return store.store; |
| 104 | }; | |
| 105 | ||
| 106 | // | |
| 107 | // ### function capitalize (str) | |
| 108 | // #### @str {string} String to capitalize | |
| 109 | // Capitalizes the specified `str`. | |
| 110 | // | |
| 111 | 1 | common.capitalize = function (str) { |
| 112 | 5 | return str && str[0].toUpperCase() + str.slice(1); |
| 113 | }; | |
| 114 |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * formats.js: Default formats supported by nconf | |
| 3 | * | |
| 4 | * (C) 2011, Nodejitsu Inc. | |
| 5 | * | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var ini = require('ini'); |
| 9 | ||
| 10 | 1 | var formats = exports; |
| 11 | ||
| 12 | // | |
| 13 | // ### @json | |
| 14 | // Standard JSON format which pretty prints `.stringify()`. | |
| 15 | // | |
| 16 | 1 | formats.json = { |
| 17 | stringify: function (obj, replacer, spacing) { | |
| 18 | 0 | return JSON.stringify(obj, replacer || null, spacing || 2) |
| 19 | }, | |
| 20 | parse: JSON.parse | |
| 21 | }; | |
| 22 | ||
| 23 | // | |
| 24 | // ### @ini | |
| 25 | // Standard INI format supplied from the `ini` module | |
| 26 | // http://en.wikipedia.org/wiki/INI_file | |
| 27 | // | |
| 28 | 1 | formats.ini = ini; |
| 29 |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * provider.js: Abstraction providing an interface into pluggable configuration storage. | |
| 3 | * | |
| 4 | * (C) 2011, Nodejitsu Inc. | |
| 5 | * | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var async = require('async'), |
| 9 | common = require('./common'); | |
| 10 | ||
| 11 | // | |
| 12 | // ### function Provider (options) | |
| 13 | // #### @options {Object} Options for this instance. | |
| 14 | // Constructor function for the Provider object responsible | |
| 15 | // for exposing the pluggable storage features of `nconf`. | |
| 16 | // | |
| 17 | 1 | var Provider = exports.Provider = function (options) { |
| 18 | // | |
| 19 | // Setup default options for working with `stores`, | |
| 20 | // `overrides`, `process.env` and `process.argv`. | |
| 21 | // | |
| 22 | 1 | options = options || {}; |
| 23 | 1 | this.stores = {}; |
| 24 | 1 | this.sources = []; |
| 25 | 1 | this.init(options); |
| 26 | }; | |
| 27 | ||
| 28 | // | |
| 29 | // Define wrapper functions for using basic stores | |
| 30 | // in this instance | |
| 31 | // | |
| 32 | 1 | ['argv', 'env'].forEach(function (type) { |
| 33 | 2 | Provider.prototype[type] = function (options) { |
| 34 | 0 | return this.add(type, options); |
| 35 | }; | |
| 36 | }); | |
| 37 | ||
| 38 | // | |
| 39 | // ### function file (key, options) | |
| 40 | // #### @key {string|Object} Fully qualified options, name of file store, or path. | |
| 41 | // #### @path {string|Object} **Optional** Full qualified options, or path. | |
| 42 | // Adds a new `File` store to this instance. Accepts the following options | |
| 43 | // | |
| 44 | // nconf.file({ file: '.jitsuconf', dir: process.env.HOME, search: true }); | |
| 45 | // nconf.file('path/to/config/file'); | |
| 46 | // nconf.file('userconfig', 'path/to/config/file'); | |
| 47 | // nconf.file('userconfig', { file: '.jitsuconf', search: true }); | |
| 48 | // | |
| 49 | 1 | Provider.prototype.file = function (key, options) { |
| 50 | 0 | if (arguments.length == 1) { |
| 51 | 0 | options = typeof key === 'string' ? { file: key } : key; |
| 52 | 0 | key = 'file'; |
| 53 | } | |
| 54 | else { | |
| 55 | 0 | options = typeof options === 'string' |
| 56 | ? { file: options } | |
| 57 | : options; | |
| 58 | } | |
| 59 | ||
| 60 | 0 | options.type = 'file'; |
| 61 | 0 | return this.add(key, options); |
| 62 | }; | |
| 63 | ||
| 64 | // | |
| 65 | // Define wrapper functions for using | |
| 66 | // overrides and defaults | |
| 67 | // | |
| 68 | 1 | ['defaults', 'overrides'].forEach(function (type) { |
| 69 | 2 | Provider.prototype[type] = function (options) { |
| 70 | 0 | options = options || {}; |
| 71 | 0 | if (!options.type) { |
| 72 | 0 | options.type = 'literal'; |
| 73 | } | |
| 74 | ||
| 75 | 0 | return this.add(type, options); |
| 76 | }; | |
| 77 | }); | |
| 78 | ||
| 79 | // | |
| 80 | // ### function use (name, options) | |
| 81 | // #### @type {string} Type of the nconf store to use. | |
| 82 | // #### @options {Object} Options for the store instance. | |
| 83 | // Adds (or replaces) a new store with the specified `name` | |
| 84 | // and `options`. If `options.type` is not set, then `name` | |
| 85 | // will be used instead: | |
| 86 | // | |
| 87 | // provider.use('file'); | |
| 88 | // provider.use('file', { type: 'file', filename: '/path/to/userconf' }) | |
| 89 | // | |
| 90 | 1 | Provider.prototype.use = function (name, options) { |
| 91 | 0 | options = options || {}; |
| 92 | 0 | var type = options.type || name; |
| 93 | ||
| 94 | 0 | function sameOptions (store) { |
| 95 | 0 | return Object.keys(options).every(function (key) { |
| 96 | 0 | return options[key] === store[key]; |
| 97 | }); | |
| 98 | } | |
| 99 | ||
| 100 | 0 | var store = this.stores[name], |
| 101 | update = store && !sameOptions(store); | |
| 102 | ||
| 103 | 0 | if (!store || update) { |
| 104 | 0 | if (update) { |
| 105 | 0 | this.remove(name); |
| 106 | } | |
| 107 | ||
| 108 | 0 | this.add(name, options); |
| 109 | } | |
| 110 | ||
| 111 | 0 | return this; |
| 112 | }; | |
| 113 | ||
| 114 | // | |
| 115 | // ### function add (name, options) | |
| 116 | // #### @name {string} Name of the store to add to this instance | |
| 117 | // #### @options {Object} Options for the store to create | |
| 118 | // Adds a new store with the specified `name` and `options`. If `options.type` | |
| 119 | // is not set, then `name` will be used instead: | |
| 120 | // | |
| 121 | // provider.add('memory'); | |
| 122 | // provider.add('userconf', { type: 'file', filename: '/path/to/userconf' }) | |
| 123 | // | |
| 124 | 1 | Provider.prototype.add = function (name, options) { |
| 125 | 0 | options = options || {}; |
| 126 | 0 | var type = options.type || name; |
| 127 | ||
| 128 | 0 | if (!require('../nconf')[common.capitalize(type)]) { |
| 129 | 0 | throw new Error('Cannot add store with unknown type: ' + type); |
| 130 | } | |
| 131 | ||
| 132 | 0 | this.stores[name] = this.create(type, options); |
| 133 | ||
| 134 | 0 | if (this.stores[name].loadSync) { |
| 135 | 0 | this.stores[name].loadSync(); |
| 136 | } | |
| 137 | ||
| 138 | 0 | return this; |
| 139 | }; | |
| 140 | ||
| 141 | // | |
| 142 | // ### function remove (name) | |
| 143 | // #### @name {string} Name of the store to remove from this instance | |
| 144 | // Removes a store with the specified `name` from this instance. Users | |
| 145 | // are allowed to pass in a type argument (e.g. `memory`) as name if | |
| 146 | // this was used in the call to `.add()`. | |
| 147 | // | |
| 148 | 1 | Provider.prototype.remove = function (name) { |
| 149 | 0 | delete this.stores[name]; |
| 150 | 0 | return this; |
| 151 | }; | |
| 152 | ||
| 153 | // | |
| 154 | // ### function create (type, options) | |
| 155 | // #### @type {string} Type of the nconf store to use. | |
| 156 | // #### @options {Object} Options for the store instance. | |
| 157 | // Creates a store of the specified `type` using the | |
| 158 | // specified `options`. | |
| 159 | // | |
| 160 | 1 | Provider.prototype.create = function (type, options) { |
| 161 | 0 | return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options); |
| 162 | }; | |
| 163 | ||
| 164 | // | |
| 165 | // ### function init (options) | |
| 166 | // #### @options {Object} Options to initialize this instance with. | |
| 167 | // Initializes this instance with additional `stores` or `sources` in the | |
| 168 | // `options` supplied. | |
| 169 | // | |
| 170 | 1 | Provider.prototype.init = function (options) { |
| 171 | 1 | var self = this; |
| 172 | ||
| 173 | // | |
| 174 | // Add any stores passed in through the options | |
| 175 | // to this instance. | |
| 176 | // | |
| 177 | 1 | if (options.type) { |
| 178 | 0 | this.add(options.type, options); |
| 179 | } | |
| 180 | 1 | else if (options.store) { |
| 181 | 0 | this.add(options.store.name || options.store.type, options.store); |
| 182 | } | |
| 183 | 1 | else if (options.stores) { |
| 184 | 0 | Object.keys(options.stores).forEach(function (name) { |
| 185 | 0 | var store = options.stores[name]; |
| 186 | 0 | self.add(store.name || name || store.type, store); |
| 187 | }); | |
| 188 | } | |
| 189 | ||
| 190 | // | |
| 191 | // Add any read-only sources to this instance | |
| 192 | // | |
| 193 | 1 | if (options.source) { |
| 194 | 0 | this.sources.push(this.create(options.source.type || options.source.name, options.source)); |
| 195 | } | |
| 196 | 1 | else if (options.sources) { |
| 197 | 0 | Object.keys(options.sources).forEach(function (name) { |
| 198 | 0 | var source = options.sources[name]; |
| 199 | 0 | self.sources.push(self.create(source.type || source.name || name, source)); |
| 200 | }); | |
| 201 | } | |
| 202 | }; | |
| 203 | ||
| 204 | // | |
| 205 | // ### function get (key, callback) | |
| 206 | // #### @key {string} Key to retrieve for this instance. | |
| 207 | // #### @callback {function} **Optional** Continuation to respond to when complete. | |
| 208 | // Retrieves the value for the specified key (if any). | |
| 209 | // | |
| 210 | 1 | Provider.prototype.get = function (key, callback) { |
| 211 | // | |
| 212 | // If there is no callback we can short-circuit into the default | |
| 213 | // logic for traversing stores. | |
| 214 | // | |
| 215 | 0 | if (!callback) { |
| 216 | 0 | return this._execute('get', 1, key, callback); |
| 217 | } | |
| 218 | ||
| 219 | // | |
| 220 | // Otherwise the asynchronous, hierarchical `get` is | |
| 221 | // slightly more complicated because we do not need to traverse | |
| 222 | // the entire set of stores, but up until there is a defined value. | |
| 223 | // | |
| 224 | 0 | var current = 0, |
| 225 | names = Object.keys(this.stores), | |
| 226 | self = this, | |
| 227 | response, | |
| 228 | mergeObjs = []; | |
| 229 | ||
| 230 | 0 | async.whilst(function () { |
| 231 | 0 | return typeof response === 'undefined' && current < names.length; |
| 232 | }, function (next) { | |
| 233 | 0 | var store = self.stores[names[current]]; |
| 234 | 0 | current++; |
| 235 | ||
| 236 | 0 | if (store.get.length >= 2) { |
| 237 | 0 | return store.get(key, function (err, value) { |
| 238 | 0 | if (err) { |
| 239 | 0 | return next(err); |
| 240 | } | |
| 241 | ||
| 242 | 0 | response = value; |
| 243 | ||
| 244 | // Merge objects if necessary | |
| 245 | 0 | if (typeof response === 'object' && !Array.isArray(response)) { |
| 246 | 0 | mergeObjs.push(response); |
| 247 | 0 | response = undefined; |
| 248 | } | |
| 249 | ||
| 250 | 0 | next(); |
| 251 | }); | |
| 252 | } | |
| 253 | ||
| 254 | 0 | response = store.get(key); |
| 255 | ||
| 256 | // Merge objects if necessary | |
| 257 | 0 | if (typeof response === 'object' && !Array.isArray(response)) { |
| 258 | 0 | mergeObjs.push(response); |
| 259 | 0 | response = undefined; |
| 260 | } | |
| 261 | ||
| 262 | 0 | next(); |
| 263 | }, function (err) { | |
| 264 | 0 | if (!err && mergeObjs.length) { |
| 265 | 0 | response = common.merge(mergeObjs.reverse()); |
| 266 | } | |
| 267 | 0 | return err ? callback(err) : callback(null, response); |
| 268 | }); | |
| 269 | }; | |
| 270 | ||
| 271 | // | |
| 272 | // ### function set (key, value, callback) | |
| 273 | // #### @key {string} Key to set in this instance | |
| 274 | // #### @value {literal|Object} Value for the specified key | |
| 275 | // #### @callback {function} **Optional** Continuation to respond to when complete. | |
| 276 | // Sets the `value` for the specified `key` in this instance. | |
| 277 | // | |
| 278 | 1 | Provider.prototype.set = function (key, value, callback) { |
| 279 | 1 | return this._execute('set', 2, key, value, callback); |
| 280 | }; | |
| 281 | ||
| 282 | // | |
| 283 | // ### function reset (callback) | |
| 284 | // #### @callback {function} **Optional** Continuation to respond to when complete. | |
| 285 | // Clears all keys associated with this instance. | |
| 286 | // | |
| 287 | 1 | Provider.prototype.reset = function (callback) { |
| 288 | 0 | return this._execute('reset', 0, callback); |
| 289 | }; | |
| 290 | ||
| 291 | // | |
| 292 | // ### function clear (key, callback) | |
| 293 | // #### @key {string} Key to remove from this instance | |
| 294 | // #### @callback {function} **Optional** Continuation to respond to when complete. | |
| 295 | // Removes the value for the specified `key` from this instance. | |
| 296 | // | |
| 297 | 1 | Provider.prototype.clear = function (key, callback) { |
| 298 | 0 | return this._execute('clear', 1, key, callback); |
| 299 | }; | |
| 300 | ||
| 301 | // | |
| 302 | // ### function merge ([key,] value [, callback]) | |
| 303 | // #### @key {string} Key to merge the value into | |
| 304 | // #### @value {literal|Object} Value to merge into the key | |
| 305 | // #### @callback {function} **Optional** Continuation to respond to when complete. | |
| 306 | // Merges the properties in `value` into the existing object value at `key`. | |
| 307 | // | |
| 308 | // 1. If the existing value `key` is not an Object, it will be completely overwritten. | |
| 309 | // 2. If `key` is not supplied, then the `value` will be merged into the root. | |
| 310 | // | |
| 311 | 1 | Provider.prototype.merge = function () { |
| 312 | 0 | var self = this, |
| 313 | args = Array.prototype.slice.call(arguments), | |
| 314 | callback = typeof args[args.length - 1] === 'function' && args.pop(), | |
| 315 | value = args.pop(), | |
| 316 | key = args.pop(); | |
| 317 | ||
| 318 | 0 | function mergeProperty (prop, next) { |
| 319 | 0 | return self._execute('merge', 2, prop, value[prop], next); |
| 320 | } | |
| 321 | ||
| 322 | 0 | if (!key) { |
| 323 | 0 | if (Array.isArray(value) || typeof value !== 'object') { |
| 324 | 0 | return onError(new Error('Cannot merge non-Object into top-level.'), callback); |
| 325 | } | |
| 326 | ||
| 327 | 0 | return async.forEach(Object.keys(value), mergeProperty, callback || function () { }) |
| 328 | } | |
| 329 | ||
| 330 | 0 | return this._execute('merge', 2, key, value, callback); |
| 331 | }; | |
| 332 | ||
| 333 | // | |
| 334 | // ### function load (callback) | |
| 335 | // #### @callback {function} Continuation to respond to when complete. | |
| 336 | // Responds with an Object representing all keys associated in this instance. | |
| 337 | // | |
| 338 | 1 | Provider.prototype.load = function (callback) { |
| 339 | 0 | var self = this; |
| 340 | ||
| 341 | 0 | function getStores () { |
| 342 | 0 | var stores = Object.keys(self.stores); |
| 343 | 0 | stores.reverse(); |
| 344 | 0 | return stores.map(function (name) { |
| 345 | 0 | return self.stores[name]; |
| 346 | }); | |
| 347 | } | |
| 348 | ||
| 349 | 0 | function loadStoreSync(store) { |
| 350 | 0 | if (!store.loadSync) { |
| 351 | 0 | throw new Error('nconf store ' + store.type + ' has no loadSync() method'); |
| 352 | } | |
| 353 | ||
| 354 | 0 | return store.loadSync(); |
| 355 | } | |
| 356 | ||
| 357 | 0 | function loadStore(store, next) { |
| 358 | 0 | if (!store.load && !store.loadSync) { |
| 359 | 0 | return next(new Error('nconf store ' + store.type + ' has no load() method')); |
| 360 | } | |
| 361 | ||
| 362 | 0 | return store.loadSync |
| 363 | ? next(null, store.loadSync()) | |
| 364 | : store.load(next); | |
| 365 | } | |
| 366 | ||
| 367 | 0 | function loadBatch (targets, done) { |
| 368 | 0 | if (!done) { |
| 369 | 0 | return common.merge(targets.map(loadStoreSync)); |
| 370 | } | |
| 371 | ||
| 372 | 0 | async.map(targets, loadStore, function (err, objs) { |
| 373 | 0 | return err ? done(err) : done(null, common.merge(objs)); |
| 374 | }); | |
| 375 | } | |
| 376 | ||
| 377 | 0 | function mergeSources (data) { |
| 378 | // | |
| 379 | // If `data` was returned then merge it into | |
| 380 | // the system store. | |
| 381 | // | |
| 382 | 0 | if (data && typeof data === 'object') { |
| 383 | 0 | self.use('sources', { |
| 384 | type: 'literal', | |
| 385 | store: data | |
| 386 | }); | |
| 387 | } | |
| 388 | } | |
| 389 | ||
| 390 | 0 | function loadSources () { |
| 391 | 0 | var sourceHierarchy = self.sources.splice(0); |
| 392 | 0 | sourceHierarchy.reverse(); |
| 393 | ||
| 394 | // | |
| 395 | // If we don't have a callback and the current | |
| 396 | // store is capable of loading synchronously | |
| 397 | // then do so. | |
| 398 | // | |
| 399 | 0 | if (!callback) { |
| 400 | 0 | mergeSources(loadBatch(sourceHierarchy)); |
| 401 | 0 | return loadBatch(getStores()); |
| 402 | } | |
| 403 | ||
| 404 | 0 | loadBatch(sourceHierarchy, function (err, data) { |
| 405 | 0 | if (err) { |
| 406 | 0 | return callback(err); |
| 407 | } | |
| 408 | ||
| 409 | 0 | mergeSources(data); |
| 410 | 0 | return loadBatch(getStores(), callback); |
| 411 | }); | |
| 412 | } | |
| 413 | ||
| 414 | 0 | return self.sources.length |
| 415 | ? loadSources() | |
| 416 | : loadBatch(getStores(), callback); | |
| 417 | }; | |
| 418 | ||
| 419 | // | |
| 420 | // ### function save (callback) | |
| 421 | // #### @callback {function} **optional** Continuation to respond to when | |
| 422 | // complete. | |
| 423 | // Instructs each provider to save. If a callback is provided, we will attempt | |
| 424 | // asynchronous saves on the providers, falling back to synchronous saves if | |
| 425 | // this isn't possible. If a provider does not know how to save, it will be | |
| 426 | // ignored. Returns an object consisting of all of the data which was | |
| 427 | // actually saved. | |
| 428 | // | |
| 429 | 1 | Provider.prototype.save = function (value, callback) { |
| 430 | 0 | if (!callback && typeof value === 'function') { |
| 431 | 0 | callback = value; |
| 432 | 0 | value = null; |
| 433 | } | |
| 434 | ||
| 435 | 0 | var self = this, |
| 436 | names = Object.keys(this.stores); | |
| 437 | ||
| 438 | 0 | function saveStoreSync(memo, name) { |
| 439 | 0 | var store = self.stores[name]; |
| 440 | ||
| 441 | // | |
| 442 | // If the `store` doesn't have a `saveSync` method, | |
| 443 | // just ignore it and continue. | |
| 444 | // | |
| 445 | 0 | if (store.saveSync) { |
| 446 | 0 | var ret = store.saveSync(); |
| 447 | 0 | if (typeof ret == 'object' && ret !== null) { |
| 448 | 0 | memo.push(ret); |
| 449 | } | |
| 450 | } | |
| 451 | 0 | return memo; |
| 452 | } | |
| 453 | ||
| 454 | 0 | function saveStore(memo, name, next) { |
| 455 | 0 | var store = self.stores[name]; |
| 456 | ||
| 457 | // | |
| 458 | // If the `store` doesn't have a `save` or saveSync` | |
| 459 | // method(s), just ignore it and continue. | |
| 460 | // | |
| 461 | ||
| 462 | 0 | if (store.save) { |
| 463 | 0 | return store.save(function (err, data) { |
| 464 | 0 | if (err) { |
| 465 | 0 | return next(err); |
| 466 | } | |
| 467 | ||
| 468 | 0 | if (typeof data == 'object' && data !== null) { |
| 469 | 0 | memo.push(data); |
| 470 | } | |
| 471 | ||
| 472 | 0 | next(null, memo); |
| 473 | }); | |
| 474 | } | |
| 475 | 0 | else if (store.saveSync) { |
| 476 | 0 | memo.push(store.saveSync()); |
| 477 | } | |
| 478 | ||
| 479 | 0 | next(null, memo); |
| 480 | } | |
| 481 | ||
| 482 | // | |
| 483 | // If we don't have a callback and the current | |
| 484 | // store is capable of saving synchronously | |
| 485 | // then do so. | |
| 486 | // | |
| 487 | 0 | if (!callback) { |
| 488 | 0 | return common.merge(names.reduce(saveStoreSync, [])); |
| 489 | } | |
| 490 | ||
| 491 | 0 | async.reduce(names, [], saveStore, function (err, objs) { |
| 492 | 0 | return err ? callback(err) : callback(null, common.merge(objs)); |
| 493 | }); | |
| 494 | }; | |
| 495 | ||
| 496 | // | |
| 497 | // ### @private function _execute (action, syncLength, [arguments]) | |
| 498 | // #### @action {string} Action to execute on `this.store`. | |
| 499 | // #### @syncLength {number} Function length of the sync version. | |
| 500 | // #### @arguments {Array} Arguments array to apply to the action | |
| 501 | // Executes the specified `action` on all stores for this instance, ensuring a callback supplied | |
| 502 | // to a synchronous store function is still invoked. | |
| 503 | // | |
| 504 | 1 | Provider.prototype._execute = function (action, syncLength /* [arguments] */) { |
| 505 | 1 | var args = Array.prototype.slice.call(arguments, 2), |
| 506 | callback = typeof args[args.length - 1] === 'function' && args.pop(), | |
| 507 | destructive = ['set', 'clear', 'merge', 'reset'].indexOf(action) !== -1, | |
| 508 | self = this, | |
| 509 | response, | |
| 510 | mergeObjs = []; | |
| 511 | ||
| 512 | 1 | function runAction (name, next) { |
| 513 | 0 | var store = self.stores[name]; |
| 514 | ||
| 515 | 0 | if (destructive && store.readOnly) { |
| 516 | 0 | return next(); |
| 517 | } | |
| 518 | ||
| 519 | 0 | return store[action].length > syncLength |
| 520 | ? store[action].apply(store, args.concat(next)) | |
| 521 | : next(null, store[action].apply(store, args)); | |
| 522 | } | |
| 523 | ||
| 524 | 1 | if (callback) { |
| 525 | 0 | return async.forEach(Object.keys(this.stores), runAction, function (err) { |
| 526 | 0 | return err ? callback(err) : callback(); |
| 527 | }); | |
| 528 | } | |
| 529 | ||
| 530 | ||
| 531 | 1 | Object.keys(this.stores).forEach(function (name) { |
| 532 | 0 | if (typeof response === 'undefined') { |
| 533 | 0 | var store = self.stores[name]; |
| 534 | ||
| 535 | 0 | if (destructive && store.readOnly) { |
| 536 | 0 | return; |
| 537 | } | |
| 538 | ||
| 539 | 0 | response = store[action].apply(store, args); |
| 540 | ||
| 541 | // Merge objects if necessary | |
| 542 | 0 | if (response && action === 'get' && typeof response === 'object' && !Array.isArray(response)) { |
| 543 | 0 | mergeObjs.push(response); |
| 544 | 0 | response = undefined; |
| 545 | } | |
| 546 | } | |
| 547 | }); | |
| 548 | ||
| 549 | 1 | if (mergeObjs.length) { |
| 550 | 0 | response = common.merge(mergeObjs.reverse()); |
| 551 | } | |
| 552 | ||
| 553 | 1 | return response; |
| 554 | } | |
| 555 | ||
| 556 | // | |
| 557 | // Throw the `err` if a callback is not supplied | |
| 558 | // | |
| 559 | 1 | function onError(err, callback) { |
| 560 | 0 | if (callback) { |
| 561 | 0 | return callback(err); |
| 562 | } | |
| 563 | ||
| 564 | 0 | throw err; |
| 565 | } | |
| 566 |
| Line | Hits | Source |
|---|---|---|
| 1 | /* | |
| 2 | * memory.js: Simple memory storage engine for nconf configuration(s) | |
| 3 | * | |
| 4 | * (C) 2011, Nodejitsu Inc. | |
| 5 | * | |
| 6 | */ | |
| 7 | ||
| 8 | 1 | var common = require('../common'); |
| 9 | ||
| 10 | // | |
| 11 | // ### function Memory (options) | |
| 12 | // #### @options {Object} Options for this instance | |
| 13 | // Constructor function for the Memory nconf store which maintains | |
| 14 | // a nested json structure based on key delimiters `:`. | |
| 15 | // | |
| 16 | // e.g. `my:nested:key` ==> `{ my: { nested: { key: } } }` | |
| 17 | // | |
| 18 | 1 | var Memory = exports.Memory = function (options) { |
| 19 | 0 | options = options || {}; |
| 20 | 0 | this.type = 'memory'; |
| 21 | 0 | this.store = {}; |
| 22 | 0 | this.mtimes = {}; |
| 23 | 0 | this.readOnly = false; |
| 24 | 0 | this.loadFrom = options.loadFrom || null; |
| 25 | ||
| 26 | 0 | if (this.loadFrom) { |
| 27 | 0 | this.store = common.loadFilesSync(this.loadFrom); |
| 28 | } | |
| 29 | }; | |
| 30 | ||
| 31 | // | |
| 32 | // ### function get (key) | |
| 33 | // #### @key {string} Key to retrieve for this instance. | |
| 34 | // Retrieves the value for the specified key (if any). | |
| 35 | // | |
| 36 | 1 | Memory.prototype.get = function (key) { |
| 37 | 0 | var target = this.store, |
| 38 | path = common.path(key); | |
| 39 | ||
| 40 | // | |
| 41 | // Scope into the object to get the appropriate nested context | |
| 42 | // | |
| 43 | 0 | while (path.length > 0) { |
| 44 | 0 | key = path.shift(); |
| 45 | 0 | if (target && target.hasOwnProperty(key)) { |
| 46 | 0 | target = target[key]; |
| 47 | 0 | continue; |
| 48 | } | |
| 49 | 0 | return undefined; |
| 50 | } | |
| 51 | ||
| 52 | 0 | return target; |
| 53 | }; | |
| 54 | ||
| 55 | // | |
| 56 | // ### function set (key, value) | |
| 57 | // #### @key {string} Key to set in this instance | |
| 58 | // #### @value {literal|Object} Value for the specified key | |
| 59 | // Sets the `value` for the specified `key` in this instance. | |
| 60 | // | |
| 61 | 1 | Memory.prototype.set = function (key, value) { |
| 62 | 0 | if (this.readOnly) { |
| 63 | 0 | return false; |
| 64 | } | |
| 65 | ||
| 66 | 0 | var target = this.store, |
| 67 | path = common.path(key); | |
| 68 | ||
| 69 | 0 | if (path.length === 0) { |
| 70 | // | |
| 71 | // Root must be an object | |
| 72 | // | |
| 73 | 0 | if (!value || typeof value !== 'object') { |
| 74 | 0 | return false; |
| 75 | } | |
| 76 | else { | |
| 77 | 0 | this.reset(); |
| 78 | 0 | this.store = value; |
| 79 | 0 | return true; |
| 80 | } | |
| 81 | } | |
| 82 | ||
| 83 | // | |
| 84 | // Update the `mtime` (modified time) of the key | |
| 85 | // | |
| 86 | 0 | this.mtimes[key] = Date.now(); |
| 87 | ||
| 88 | // | |
| 89 | // Scope into the object to get the appropriate nested context | |
| 90 | // | |
| 91 | 0 | while (path.length > 1) { |
| 92 | 0 | key = path.shift(); |
| 93 | 0 | if (!target[key] || typeof target[key] !== 'object') { |
| 94 | 0 | target[key] = {}; |
| 95 | } | |
| 96 | ||
| 97 | 0 | target = target[key]; |
| 98 | } | |
| 99 | ||
| 100 | // Set the specified value in the nested JSON structure | |
| 101 | 0 | key = path.shift(); |
| 102 | 0 | target[key] = value; |
| 103 | 0 | return true; |
| 104 | }; | |
| 105 | ||
| 106 | // | |
| 107 | // ### function clear (key) | |
| 108 | // #### @key {string} Key to remove from this instance | |
| 109 | // Removes the value for the specified `key` from this instance. | |
| 110 | // | |
| 111 | 1 | Memory.prototype.clear = function (key) { |
| 112 | 0 | if (this.readOnly) { |
| 113 | 0 | return false; |
| 114 | } | |
| 115 | ||
| 116 | 0 | var target = this.store, |
| 117 | value = target, | |
| 118 | path = common.path(key); | |
| 119 | ||
| 120 | // | |
| 121 | // Remove the key from the set of `mtimes` (modified times) | |
| 122 | // | |
| 123 | 0 | delete this.mtimes[key]; |
| 124 | ||
| 125 | // | |
| 126 | // Scope into the object to get the appropriate nested context | |
| 127 | // | |
| 128 | 0 | for (var i = 0; i < path.length - 1; i++) { |
| 129 | 0 | key = path[i]; |
| 130 | 0 | value = target[key]; |
| 131 | 0 | if (typeof value !== 'function' && typeof value !== 'object') { |
| 132 | 0 | return false; |
| 133 | } | |
| 134 | 0 | target = value; |
| 135 | } | |
| 136 | ||
| 137 | // Delete the key from the nested JSON structure | |
| 138 | 0 | key = path[i]; |
| 139 | 0 | delete target[key]; |
| 140 | 0 | return true; |
| 141 | }; | |
| 142 | ||
| 143 | // | |
| 144 | // ### function merge (key, value) | |
| 145 | // #### @key {string} Key to merge the value into | |
| 146 | // #### @value {literal|Object} Value to merge into the key | |
| 147 | // Merges the properties in `value` into the existing object value | |
| 148 | // at `key`. If the existing value `key` is not an Object, it will be | |
| 149 | // completely overwritten. | |
| 150 | // | |
| 151 | 1 | Memory.prototype.merge = function (key, value) { |
| 152 | 0 | if (this.readOnly) { |
| 153 | 0 | return false; |
| 154 | } | |
| 155 | ||
| 156 | // | |
| 157 | // If the key is not an `Object` or is an `Array`, | |
| 158 | // then simply set it. Merging is for Objects. | |
| 159 | // | |
| 160 | 0 | if (typeof value !== 'object' || Array.isArray(value) || value === null) { |
| 161 | 0 | return this.set(key, value); |
| 162 | } | |
| 163 | ||
| 164 | 0 | var self = this, |
| 165 | target = this.store, | |
| 166 | path = common.path(key), | |
| 167 | fullKey = key; | |
| 168 | ||
| 169 | // | |
| 170 | // Update the `mtime` (modified time) of the key | |
| 171 | // | |
| 172 | 0 | this.mtimes[key] = Date.now(); |
| 173 | ||
| 174 | // | |
| 175 | // Scope into the object to get the appropriate nested context | |
| 176 | // | |
| 177 | 0 | while (path.length > 1) { |
| 178 | 0 | key = path.shift(); |
| 179 | 0 | if (!target[key]) { |
| 180 | 0 | target[key] = {}; |
| 181 | } | |
| 182 | ||
| 183 | 0 | target = target[key]; |
| 184 | } | |
| 185 | ||
| 186 | // Set the specified value in the nested JSON structure | |
| 187 | 0 | key = path.shift(); |
| 188 | ||
| 189 | // | |
| 190 | // If the current value at the key target is not an `Object`, | |
| 191 | // or is an `Array` then simply override it because the new value | |
| 192 | // is an Object. | |
| 193 | // | |
| 194 | 0 | if (typeof target[key] !== 'object' || Array.isArray(target[key])) { |
| 195 | 0 | target[key] = value; |
| 196 | 0 | return true; |
| 197 | } | |
| 198 | ||
| 199 | 0 | return Object.keys(value).every(function (nested) { |
| 200 | 0 | return self.merge(common.key(fullKey, nested), value[nested]); |
| 201 | }); | |
| 202 | }; | |
| 203 | ||
| 204 | // | |
| 205 | // ### function reset (callback) | |
| 206 | // Clears all keys associated with this instance. | |
| 207 | // | |
| 208 | 1 | Memory.prototype.reset = function () { |
| 209 | 0 | if (this.readOnly) { |
| 210 | 0 | return false; |
| 211 | } | |
| 212 | ||
| 213 | 0 | this.mtimes = {}; |
| 214 | 0 | this.store = {}; |
| 215 | 0 | return true; |
| 216 | }; | |
| 217 | ||
| 218 | // | |
| 219 | // ### function loadSync | |
| 220 | // Returns the store managed by this instance | |
| 221 | // | |
| 222 | 1 | Memory.prototype.loadSync = function () { |
| 223 | 0 | return this.store || {}; |
| 224 | }; | |
| 225 |
| Line | Hits | Source |
|---|---|---|
| 1 | /*global setImmediate: false, setTimeout: false, console: false */ | |
| 2 | 1 | (function () { |
| 3 | ||
| 4 | 1 | var async = {}; |
| 5 | ||
| 6 | // global on the server, window in the browser | |
| 7 | 1 | var root, previous_async; |
| 8 | ||
| 9 | 1 | root = this; |
| 10 | 1 | if (root != null) { |
| 11 | 1 | previous_async = root.async; |
| 12 | } | |
| 13 | ||
| 14 | 1 | async.noConflict = function () { |
| 15 | 0 | root.async = previous_async; |
| 16 | 0 | return async; |
| 17 | }; | |
| 18 | ||
| 19 | 1 | function only_once(fn) { |
| 20 | 0 | var called = false; |
| 21 | 0 | return function() { |
| 22 | 0 | if (called) throw new Error("Callback was already called."); |
| 23 | 0 | called = true; |
| 24 | 0 | fn.apply(root, arguments); |
| 25 | } | |
| 26 | } | |
| 27 | ||
| 28 | //// cross-browser compatiblity functions //// | |
| 29 | ||
| 30 | 1 | var _each = function (arr, iterator) { |
| 31 | 0 | if (arr.forEach) { |
| 32 | 0 | return arr.forEach(iterator); |
| 33 | } | |
| 34 | 0 | for (var i = 0; i < arr.length; i += 1) { |
| 35 | 0 | iterator(arr[i], i, arr); |
| 36 | } | |
| 37 | }; | |
| 38 | ||
| 39 | 1 | var _map = function (arr, iterator) { |
| 40 | 0 | if (arr.map) { |
| 41 | 0 | return arr.map(iterator); |
| 42 | } | |
| 43 | 0 | var results = []; |
| 44 | 0 | _each(arr, function (x, i, a) { |
| 45 | 0 | results.push(iterator(x, i, a)); |
| 46 | }); | |
| 47 | 0 | return results; |
| 48 | }; | |
| 49 | ||
| 50 | 1 | var _reduce = function (arr, iterator, memo) { |
| 51 | 0 | if (arr.reduce) { |
| 52 | 0 | return arr.reduce(iterator, memo); |
| 53 | } | |
| 54 | 0 | _each(arr, function (x, i, a) { |
| 55 | 0 | memo = iterator(memo, x, i, a); |
| 56 | }); | |
| 57 | 0 | return memo; |
| 58 | }; | |
| 59 | ||
| 60 | 1 | var _keys = function (obj) { |
| 61 | 0 | if (Object.keys) { |
| 62 | 0 | return Object.keys(obj); |
| 63 | } | |
| 64 | 0 | var keys = []; |
| 65 | 0 | for (var k in obj) { |
| 66 | 0 | if (obj.hasOwnProperty(k)) { |
| 67 | 0 | keys.push(k); |
| 68 | } | |
| 69 | } | |
| 70 | 0 | return keys; |
| 71 | }; | |
| 72 | ||
| 73 | //// exported async module functions //// | |
| 74 | ||
| 75 | //// nextTick implementation with browser-compatible fallback //// | |
| 76 | 1 | if (typeof process === 'undefined' || !(process.nextTick)) { |
| 77 | 0 | if (typeof setImmediate === 'function') { |
| 78 | 0 | async.nextTick = function (fn) { |
| 79 | // not a direct alias for IE10 compatibility | |
| 80 | 0 | setImmediate(fn); |
| 81 | }; | |
| 82 | 0 | async.setImmediate = async.nextTick; |
| 83 | } | |
| 84 | else { | |
| 85 | 0 | async.nextTick = function (fn) { |
| 86 | 0 | setTimeout(fn, 0); |
| 87 | }; | |
| 88 | 0 | async.setImmediate = async.nextTick; |
| 89 | } | |
| 90 | } | |
| 91 | else { | |
| 92 | 1 | async.nextTick = process.nextTick; |
| 93 | 1 | if (typeof setImmediate !== 'undefined') { |
| 94 | 1 | async.setImmediate = setImmediate; |
| 95 | } | |
| 96 | else { | |
| 97 | 0 | async.setImmediate = async.nextTick; |
| 98 | } | |
| 99 | } | |
| 100 | ||
| 101 | 1 | async.each = function (arr, iterator, callback) { |
| 102 | 0 | callback = callback || function () {}; |
| 103 | 0 | if (!arr.length) { |
| 104 | 0 | return callback(); |
| 105 | } | |
| 106 | 0 | var completed = 0; |
| 107 | 0 | _each(arr, function (x) { |
| 108 | 0 | iterator(x, only_once(function (err) { |
| 109 | 0 | if (err) { |
| 110 | 0 | callback(err); |
| 111 | 0 | callback = function () {}; |
| 112 | } | |
| 113 | else { | |
| 114 | 0 | completed += 1; |
| 115 | 0 | if (completed >= arr.length) { |
| 116 | 0 | callback(null); |
| 117 | } | |
| 118 | } | |
| 119 | })); | |
| 120 | }); | |
| 121 | }; | |
| 122 | 1 | async.forEach = async.each; |
| 123 | ||
| 124 | 1 | async.eachSeries = function (arr, iterator, callback) { |
| 125 | 0 | callback = callback || function () {}; |
| 126 | 0 | if (!arr.length) { |
| 127 | 0 | return callback(); |
| 128 | } | |
| 129 | 0 | var completed = 0; |
| 130 | 0 | var iterate = function () { |
| 131 | 0 | iterator(arr[completed], function (err) { |
| 132 | 0 | if (err) { |
| 133 | 0 | callback(err); |
| 134 | 0 | callback = function () {}; |
| 135 | } | |
| 136 | else { | |
| 137 | 0 | completed += 1; |
| 138 | 0 | if (completed >= arr.length) { |
| 139 | 0 | callback(null); |
| 140 | } | |
| 141 | else { | |
| 142 | 0 | iterate(); |
| 143 | } | |
| 144 | } | |
| 145 | }); | |
| 146 | }; | |
| 147 | 0 | iterate(); |
| 148 | }; | |
| 149 | 1 | async.forEachSeries = async.eachSeries; |
| 150 | ||
| 151 | 1 | async.eachLimit = function (arr, limit, iterator, callback) { |
| 152 | 0 | var fn = _eachLimit(limit); |
| 153 | 0 | fn.apply(null, [arr, iterator, callback]); |
| 154 | }; | |
| 155 | 1 | async.forEachLimit = async.eachLimit; |
| 156 | ||
| 157 | 1 | var _eachLimit = function (limit) { |
| 158 | ||
| 159 | 0 | return function (arr, iterator, callback) { |
| 160 | 0 | callback = callback || function () {}; |
| 161 | 0 | if (!arr.length || limit <= 0) { |
| 162 | 0 | return callback(); |
| 163 | } | |
| 164 | 0 | var completed = 0; |
| 165 | 0 | var started = 0; |
| 166 | 0 | var running = 0; |
| 167 | ||
| 168 | 0 | (function replenish () { |
| 169 | 0 | if (completed >= arr.length) { |
| 170 | 0 | return callback(); |
| 171 | } | |
| 172 | ||
| 173 | 0 | while (running < limit && started < arr.length) { |
| 174 | 0 | started += 1; |
| 175 | 0 | running += 1; |
| 176 | 0 | iterator(arr[started - 1], function (err) { |
| 177 | 0 | if (err) { |
| 178 | 0 | callback(err); |
| 179 | 0 | callback = function () {}; |
| 180 | } | |
| 181 | else { | |
| 182 | 0 | completed += 1; |
| 183 | 0 | running -= 1; |
| 184 | 0 | if (completed >= arr.length) { |
| 185 | 0 | callback(); |
| 186 | } | |
| 187 | else { | |
| 188 | 0 | replenish(); |
| 189 | } | |
| 190 | } | |
| 191 | }); | |
| 192 | } | |
| 193 | })(); | |
| 194 | }; | |
| 195 | }; | |
| 196 | ||
| 197 | ||
| 198 | 1 | var doParallel = function (fn) { |
| 199 | 6 | return function () { |
| 200 | 0 | var args = Array.prototype.slice.call(arguments); |
| 201 | 0 | return fn.apply(null, [async.each].concat(args)); |
| 202 | }; | |
| 203 | }; | |
| 204 | 1 | var doParallelLimit = function(limit, fn) { |
| 205 | 0 | return function () { |
| 206 | 0 | var args = Array.prototype.slice.call(arguments); |
| 207 | 0 | return fn.apply(null, [_eachLimit(limit)].concat(args)); |
| 208 | }; | |
| 209 | }; | |
| 210 | 1 | var doSeries = function (fn) { |
| 211 | 6 | return function () { |
| 212 | 0 | var args = Array.prototype.slice.call(arguments); |
| 213 | 0 | return fn.apply(null, [async.eachSeries].concat(args)); |
| 214 | }; | |
| 215 | }; | |
| 216 | ||
| 217 | ||
| 218 | 1 | var _asyncMap = function (eachfn, arr, iterator, callback) { |
| 219 | 0 | var results = []; |
| 220 | 0 | arr = _map(arr, function (x, i) { |
| 221 | 0 | return {index: i, value: x}; |
| 222 | }); | |
| 223 | 0 | eachfn(arr, function (x, callback) { |
| 224 | 0 | iterator(x.value, function (err, v) { |
| 225 | 0 | results[x.index] = v; |
| 226 | 0 | callback(err); |
| 227 | }); | |
| 228 | }, function (err) { | |
| 229 | 0 | callback(err, results); |
| 230 | }); | |
| 231 | }; | |
| 232 | 1 | async.map = doParallel(_asyncMap); |
| 233 | 1 | async.mapSeries = doSeries(_asyncMap); |
| 234 | 1 | async.mapLimit = function (arr, limit, iterator, callback) { |
| 235 | 0 | return _mapLimit(limit)(arr, iterator, callback); |
| 236 | }; | |
| 237 | ||
| 238 | 1 | var _mapLimit = function(limit) { |
| 239 | 0 | return doParallelLimit(limit, _asyncMap); |
| 240 | }; | |
| 241 | ||
| 242 | // reduce only has a series version, as doing reduce in parallel won't | |
| 243 | // work in many situations. | |
| 244 | 1 | async.reduce = function (arr, memo, iterator, callback) { |
| 245 | 0 | async.eachSeries(arr, function (x, callback) { |
| 246 | 0 | iterator(memo, x, function (err, v) { |
| 247 | 0 | memo = v; |
| 248 | 0 | callback(err); |
| 249 | }); | |
| 250 | }, function (err) { | |
| 251 | 0 | callback(err, memo); |
| 252 | }); | |
| 253 | }; | |
| 254 | // inject alias | |
| 255 | 1 | async.inject = async.reduce; |
| 256 | // foldl alias | |
| 257 | 1 | async.foldl = async.reduce; |
| 258 | ||
| 259 | 1 | async.reduceRight = function (arr, memo, iterator, callback) { |
| 260 | 0 | var reversed = _map(arr, function (x) { |
| 261 | 0 | return x; |
| 262 | }).reverse(); | |
| 263 | 0 | async.reduce(reversed, memo, iterator, callback); |
| 264 | }; | |
| 265 | // foldr alias | |
| 266 | 1 | async.foldr = async.reduceRight; |
| 267 | ||
| 268 | 1 | var _filter = function (eachfn, arr, iterator, callback) { |
| 269 | 0 | var results = []; |
| 270 | 0 | arr = _map(arr, function (x, i) { |
| 271 | 0 | return {index: i, value: x}; |
| 272 | }); | |
| 273 | 0 | eachfn(arr, function (x, callback) { |
| 274 | 0 | iterator(x.value, function (v) { |
| 275 | 0 | if (v) { |
| 276 | 0 | results.push(x); |
| 277 | } | |
| 278 | 0 | callback(); |
| 279 | }); | |
| 280 | }, function (err) { | |
| 281 | 0 | callback(_map(results.sort(function (a, b) { |
| 282 | 0 | return a.index - b.index; |
| 283 | }), function (x) { | |
| 284 | 0 | return x.value; |
| 285 | })); | |
| 286 | }); | |
| 287 | }; | |
| 288 | 1 | async.filter = doParallel(_filter); |
| 289 | 1 | async.filterSeries = doSeries(_filter); |
| 290 | // select alias | |
| 291 | 1 | async.select = async.filter; |
| 292 | 1 | async.selectSeries = async.filterSeries; |
| 293 | ||
| 294 | 1 | var _reject = function (eachfn, arr, iterator, callback) { |
| 295 | 0 | var results = []; |
| 296 | 0 | arr = _map(arr, function (x, i) { |
| 297 | 0 | return {index: i, value: x}; |
| 298 | }); | |
| 299 | 0 | eachfn(arr, function (x, callback) { |
| 300 | 0 | iterator(x.value, function (v) { |
| 301 | 0 | if (!v) { |
| 302 | 0 | results.push(x); |
| 303 | } | |
| 304 | 0 | callback(); |
| 305 | }); | |
| 306 | }, function (err) { | |
| 307 | 0 | callback(_map(results.sort(function (a, b) { |
| 308 | 0 | return a.index - b.index; |
| 309 | }), function (x) { | |
| 310 | 0 | return x.value; |
| 311 | })); | |
| 312 | }); | |
| 313 | }; | |
| 314 | 1 | async.reject = doParallel(_reject); |
| 315 | 1 | async.rejectSeries = doSeries(_reject); |
| 316 | ||
| 317 | 1 | var _detect = function (eachfn, arr, iterator, main_callback) { |
| 318 | 0 | eachfn(arr, function (x, callback) { |
| 319 | 0 | iterator(x, function (result) { |
| 320 | 0 | if (result) { |
| 321 | 0 | main_callback(x); |
| 322 | 0 | main_callback = function () {}; |
| 323 | } | |
| 324 | else { | |
| 325 | 0 | callback(); |
| 326 | } | |
| 327 | }); | |
| 328 | }, function (err) { | |
| 329 | 0 | main_callback(); |
| 330 | }); | |
| 331 | }; | |
| 332 | 1 | async.detect = doParallel(_detect); |
| 333 | 1 | async.detectSeries = doSeries(_detect); |
| 334 | ||
| 335 | 1 | async.some = function (arr, iterator, main_callback) { |
| 336 | 0 | async.each(arr, function (x, callback) { |
| 337 | 0 | iterator(x, function (v) { |
| 338 | 0 | if (v) { |
| 339 | 0 | main_callback(true); |
| 340 | 0 | main_callback = function () {}; |
| 341 | } | |
| 342 | 0 | callback(); |
| 343 | }); | |
| 344 | }, function (err) { | |
| 345 | 0 | main_callback(false); |
| 346 | }); | |
| 347 | }; | |
| 348 | // any alias | |
| 349 | 1 | async.any = async.some; |
| 350 | ||
| 351 | 1 | async.every = function (arr, iterator, main_callback) { |
| 352 | 0 | async.each(arr, function (x, callback) { |
| 353 | 0 | iterator(x, function (v) { |
| 354 | 0 | if (!v) { |
| 355 | 0 | main_callback(false); |
| 356 | 0 | main_callback = function () {}; |
| 357 | } | |
| 358 | 0 | callback(); |
| 359 | }); | |
| 360 | }, function (err) { | |
| 361 | 0 | main_callback(true); |
| 362 | }); | |
| 363 | }; | |
| 364 | // all alias | |
| 365 | 1 | async.all = async.every; |
| 366 | ||
| 367 | 1 | async.sortBy = function (arr, iterator, callback) { |
| 368 | 0 | async.map(arr, function (x, callback) { |
| 369 | 0 | iterator(x, function (err, criteria) { |
| 370 | 0 | if (err) { |
| 371 | 0 | callback(err); |
| 372 | } | |
| 373 | else { | |
| 374 | 0 | callback(null, {value: x, criteria: criteria}); |
| 375 | } | |
| 376 | }); | |
| 377 | }, function (err, results) { | |
| 378 | 0 | if (err) { |
| 379 | 0 | return callback(err); |
| 380 | } | |
| 381 | else { | |
| 382 | 0 | var fn = function (left, right) { |
| 383 | 0 | var a = left.criteria, b = right.criteria; |
| 384 | 0 | return a < b ? -1 : a > b ? 1 : 0; |
| 385 | }; | |
| 386 | 0 | callback(null, _map(results.sort(fn), function (x) { |
| 387 | 0 | return x.value; |
| 388 | })); | |
| 389 | } | |
| 390 | }); | |
| 391 | }; | |
| 392 | ||
| 393 | 1 | async.auto = function (tasks, callback) { |
| 394 | 0 | callback = callback || function () {}; |
| 395 | 0 | var keys = _keys(tasks); |
| 396 | 0 | if (!keys.length) { |
| 397 | 0 | return callback(null); |
| 398 | } | |
| 399 | ||
| 400 | 0 | var results = {}; |
| 401 | ||
| 402 | 0 | var listeners = []; |
| 403 | 0 | var addListener = function (fn) { |
| 404 | 0 | listeners.unshift(fn); |
| 405 | }; | |
| 406 | 0 | var removeListener = function (fn) { |
| 407 | 0 | for (var i = 0; i < listeners.length; i += 1) { |
| 408 | 0 | if (listeners[i] === fn) { |
| 409 | 0 | listeners.splice(i, 1); |
| 410 | 0 | return; |
| 411 | } | |
| 412 | } | |
| 413 | }; | |
| 414 | 0 | var taskComplete = function () { |
| 415 | 0 | _each(listeners.slice(0), function (fn) { |
| 416 | 0 | fn(); |
| 417 | }); | |
| 418 | }; | |
| 419 | ||
| 420 | 0 | addListener(function () { |
| 421 | 0 | if (_keys(results).length === keys.length) { |
| 422 | 0 | callback(null, results); |
| 423 | 0 | callback = function () {}; |
| 424 | } | |
| 425 | }); | |
| 426 | ||
| 427 | 0 | _each(keys, function (k) { |
| 428 | 0 | var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; |
| 429 | 0 | var taskCallback = function (err) { |
| 430 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 431 | 0 | if (args.length <= 1) { |
| 432 | 0 | args = args[0]; |
| 433 | } | |
| 434 | 0 | if (err) { |
| 435 | 0 | var safeResults = {}; |
| 436 | 0 | _each(_keys(results), function(rkey) { |
| 437 | 0 | safeResults[rkey] = results[rkey]; |
| 438 | }); | |
| 439 | 0 | safeResults[k] = args; |
| 440 | 0 | callback(err, safeResults); |
| 441 | // stop subsequent errors hitting callback multiple times | |
| 442 | 0 | callback = function () {}; |
| 443 | } | |
| 444 | else { | |
| 445 | 0 | results[k] = args; |
| 446 | 0 | async.setImmediate(taskComplete); |
| 447 | } | |
| 448 | }; | |
| 449 | 0 | var requires = task.slice(0, Math.abs(task.length - 1)) || []; |
| 450 | 0 | var ready = function () { |
| 451 | 0 | return _reduce(requires, function (a, x) { |
| 452 | 0 | return (a && results.hasOwnProperty(x)); |
| 453 | }, true) && !results.hasOwnProperty(k); | |
| 454 | }; | |
| 455 | 0 | if (ready()) { |
| 456 | 0 | task[task.length - 1](taskCallback, results); |
| 457 | } | |
| 458 | else { | |
| 459 | 0 | var listener = function () { |
| 460 | 0 | if (ready()) { |
| 461 | 0 | removeListener(listener); |
| 462 | 0 | task[task.length - 1](taskCallback, results); |
| 463 | } | |
| 464 | }; | |
| 465 | 0 | addListener(listener); |
| 466 | } | |
| 467 | }); | |
| 468 | }; | |
| 469 | ||
| 470 | 1 | async.waterfall = function (tasks, callback) { |
| 471 | 0 | callback = callback || function () {}; |
| 472 | 0 | if (tasks.constructor !== Array) { |
| 473 | 0 | var err = new Error('First argument to waterfall must be an array of functions'); |
| 474 | 0 | return callback(err); |
| 475 | } | |
| 476 | 0 | if (!tasks.length) { |
| 477 | 0 | return callback(); |
| 478 | } | |
| 479 | 0 | var wrapIterator = function (iterator) { |
| 480 | 0 | return function (err) { |
| 481 | 0 | if (err) { |
| 482 | 0 | callback.apply(null, arguments); |
| 483 | 0 | callback = function () {}; |
| 484 | } | |
| 485 | else { | |
| 486 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 487 | 0 | var next = iterator.next(); |
| 488 | 0 | if (next) { |
| 489 | 0 | args.push(wrapIterator(next)); |
| 490 | } | |
| 491 | else { | |
| 492 | 0 | args.push(callback); |
| 493 | } | |
| 494 | 0 | async.setImmediate(function () { |
| 495 | 0 | iterator.apply(null, args); |
| 496 | }); | |
| 497 | } | |
| 498 | }; | |
| 499 | }; | |
| 500 | 0 | wrapIterator(async.iterator(tasks))(); |
| 501 | }; | |
| 502 | ||
| 503 | 1 | var _parallel = function(eachfn, tasks, callback) { |
| 504 | 0 | callback = callback || function () {}; |
| 505 | 0 | if (tasks.constructor === Array) { |
| 506 | 0 | eachfn.map(tasks, function (fn, callback) { |
| 507 | 0 | if (fn) { |
| 508 | 0 | fn(function (err) { |
| 509 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 510 | 0 | if (args.length <= 1) { |
| 511 | 0 | args = args[0]; |
| 512 | } | |
| 513 | 0 | callback.call(null, err, args); |
| 514 | }); | |
| 515 | } | |
| 516 | }, callback); | |
| 517 | } | |
| 518 | else { | |
| 519 | 0 | var results = {}; |
| 520 | 0 | eachfn.each(_keys(tasks), function (k, callback) { |
| 521 | 0 | tasks[k](function (err) { |
| 522 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 523 | 0 | if (args.length <= 1) { |
| 524 | 0 | args = args[0]; |
| 525 | } | |
| 526 | 0 | results[k] = args; |
| 527 | 0 | callback(err); |
| 528 | }); | |
| 529 | }, function (err) { | |
| 530 | 0 | callback(err, results); |
| 531 | }); | |
| 532 | } | |
| 533 | }; | |
| 534 | ||
| 535 | 1 | async.parallel = function (tasks, callback) { |
| 536 | 0 | _parallel({ map: async.map, each: async.each }, tasks, callback); |
| 537 | }; | |
| 538 | ||
| 539 | 1 | async.parallelLimit = function(tasks, limit, callback) { |
| 540 | 0 | _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); |
| 541 | }; | |
| 542 | ||
| 543 | 1 | async.series = function (tasks, callback) { |
| 544 | 0 | callback = callback || function () {}; |
| 545 | 0 | if (tasks.constructor === Array) { |
| 546 | 0 | async.mapSeries(tasks, function (fn, callback) { |
| 547 | 0 | if (fn) { |
| 548 | 0 | fn(function (err) { |
| 549 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 550 | 0 | if (args.length <= 1) { |
| 551 | 0 | args = args[0]; |
| 552 | } | |
| 553 | 0 | callback.call(null, err, args); |
| 554 | }); | |
| 555 | } | |
| 556 | }, callback); | |
| 557 | } | |
| 558 | else { | |
| 559 | 0 | var results = {}; |
| 560 | 0 | async.eachSeries(_keys(tasks), function (k, callback) { |
| 561 | 0 | tasks[k](function (err) { |
| 562 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 563 | 0 | if (args.length <= 1) { |
| 564 | 0 | args = args[0]; |
| 565 | } | |
| 566 | 0 | results[k] = args; |
| 567 | 0 | callback(err); |
| 568 | }); | |
| 569 | }, function (err) { | |
| 570 | 0 | callback(err, results); |
| 571 | }); | |
| 572 | } | |
| 573 | }; | |
| 574 | ||
| 575 | 1 | async.iterator = function (tasks) { |
| 576 | 0 | var makeCallback = function (index) { |
| 577 | 0 | var fn = function () { |
| 578 | 0 | if (tasks.length) { |
| 579 | 0 | tasks[index].apply(null, arguments); |
| 580 | } | |
| 581 | 0 | return fn.next(); |
| 582 | }; | |
| 583 | 0 | fn.next = function () { |
| 584 | 0 | return (index < tasks.length - 1) ? makeCallback(index + 1): null; |
| 585 | }; | |
| 586 | 0 | return fn; |
| 587 | }; | |
| 588 | 0 | return makeCallback(0); |
| 589 | }; | |
| 590 | ||
| 591 | 1 | async.apply = function (fn) { |
| 592 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 593 | 0 | return function () { |
| 594 | 0 | return fn.apply( |
| 595 | null, args.concat(Array.prototype.slice.call(arguments)) | |
| 596 | ); | |
| 597 | }; | |
| 598 | }; | |
| 599 | ||
| 600 | 1 | var _concat = function (eachfn, arr, fn, callback) { |
| 601 | 0 | var r = []; |
| 602 | 0 | eachfn(arr, function (x, cb) { |
| 603 | 0 | fn(x, function (err, y) { |
| 604 | 0 | r = r.concat(y || []); |
| 605 | 0 | cb(err); |
| 606 | }); | |
| 607 | }, function (err) { | |
| 608 | 0 | callback(err, r); |
| 609 | }); | |
| 610 | }; | |
| 611 | 1 | async.concat = doParallel(_concat); |
| 612 | 1 | async.concatSeries = doSeries(_concat); |
| 613 | ||
| 614 | 1 | async.whilst = function (test, iterator, callback) { |
| 615 | 0 | if (test()) { |
| 616 | 0 | iterator(function (err) { |
| 617 | 0 | if (err) { |
| 618 | 0 | return callback(err); |
| 619 | } | |
| 620 | 0 | async.whilst(test, iterator, callback); |
| 621 | }); | |
| 622 | } | |
| 623 | else { | |
| 624 | 0 | callback(); |
| 625 | } | |
| 626 | }; | |
| 627 | ||
| 628 | 1 | async.doWhilst = function (iterator, test, callback) { |
| 629 | 0 | iterator(function (err) { |
| 630 | 0 | if (err) { |
| 631 | 0 | return callback(err); |
| 632 | } | |
| 633 | 0 | if (test()) { |
| 634 | 0 | async.doWhilst(iterator, test, callback); |
| 635 | } | |
| 636 | else { | |
| 637 | 0 | callback(); |
| 638 | } | |
| 639 | }); | |
| 640 | }; | |
| 641 | ||
| 642 | 1 | async.until = function (test, iterator, callback) { |
| 643 | 0 | if (!test()) { |
| 644 | 0 | iterator(function (err) { |
| 645 | 0 | if (err) { |
| 646 | 0 | return callback(err); |
| 647 | } | |
| 648 | 0 | async.until(test, iterator, callback); |
| 649 | }); | |
| 650 | } | |
| 651 | else { | |
| 652 | 0 | callback(); |
| 653 | } | |
| 654 | }; | |
| 655 | ||
| 656 | 1 | async.doUntil = function (iterator, test, callback) { |
| 657 | 0 | iterator(function (err) { |
| 658 | 0 | if (err) { |
| 659 | 0 | return callback(err); |
| 660 | } | |
| 661 | 0 | if (!test()) { |
| 662 | 0 | async.doUntil(iterator, test, callback); |
| 663 | } | |
| 664 | else { | |
| 665 | 0 | callback(); |
| 666 | } | |
| 667 | }); | |
| 668 | }; | |
| 669 | ||
| 670 | 1 | async.queue = function (worker, concurrency) { |
| 671 | 0 | if (concurrency === undefined) { |
| 672 | 0 | concurrency = 1; |
| 673 | } | |
| 674 | 0 | function _insert(q, data, pos, callback) { |
| 675 | 0 | if(data.constructor !== Array) { |
| 676 | 0 | data = [data]; |
| 677 | } | |
| 678 | 0 | _each(data, function(task) { |
| 679 | 0 | var item = { |
| 680 | data: task, | |
| 681 | callback: typeof callback === 'function' ? callback : null | |
| 682 | }; | |
| 683 | ||
| 684 | 0 | if (pos) { |
| 685 | 0 | q.tasks.unshift(item); |
| 686 | } else { | |
| 687 | 0 | q.tasks.push(item); |
| 688 | } | |
| 689 | ||
| 690 | 0 | if (q.saturated && q.tasks.length === concurrency) { |
| 691 | 0 | q.saturated(); |
| 692 | } | |
| 693 | 0 | async.setImmediate(q.process); |
| 694 | }); | |
| 695 | } | |
| 696 | ||
| 697 | 0 | var workers = 0; |
| 698 | 0 | var q = { |
| 699 | tasks: [], | |
| 700 | concurrency: concurrency, | |
| 701 | saturated: null, | |
| 702 | empty: null, | |
| 703 | drain: null, | |
| 704 | push: function (data, callback) { | |
| 705 | 0 | _insert(q, data, false, callback); |
| 706 | }, | |
| 707 | unshift: function (data, callback) { | |
| 708 | 0 | _insert(q, data, true, callback); |
| 709 | }, | |
| 710 | process: function () { | |
| 711 | 0 | if (workers < q.concurrency && q.tasks.length) { |
| 712 | 0 | var task = q.tasks.shift(); |
| 713 | 0 | if (q.empty && q.tasks.length === 0) { |
| 714 | 0 | q.empty(); |
| 715 | } | |
| 716 | 0 | workers += 1; |
| 717 | 0 | var next = function () { |
| 718 | 0 | workers -= 1; |
| 719 | 0 | if (task.callback) { |
| 720 | 0 | task.callback.apply(task, arguments); |
| 721 | } | |
| 722 | 0 | if (q.drain && q.tasks.length + workers === 0) { |
| 723 | 0 | q.drain(); |
| 724 | } | |
| 725 | 0 | q.process(); |
| 726 | }; | |
| 727 | 0 | var cb = only_once(next); |
| 728 | 0 | worker(task.data, cb); |
| 729 | } | |
| 730 | }, | |
| 731 | length: function () { | |
| 732 | 0 | return q.tasks.length; |
| 733 | }, | |
| 734 | running: function () { | |
| 735 | 0 | return workers; |
| 736 | } | |
| 737 | }; | |
| 738 | 0 | return q; |
| 739 | }; | |
| 740 | ||
| 741 | 1 | async.cargo = function (worker, payload) { |
| 742 | 0 | var working = false, |
| 743 | tasks = []; | |
| 744 | ||
| 745 | 0 | var cargo = { |
| 746 | tasks: tasks, | |
| 747 | payload: payload, | |
| 748 | saturated: null, | |
| 749 | empty: null, | |
| 750 | drain: null, | |
| 751 | push: function (data, callback) { | |
| 752 | 0 | if(data.constructor !== Array) { |
| 753 | 0 | data = [data]; |
| 754 | } | |
| 755 | 0 | _each(data, function(task) { |
| 756 | 0 | tasks.push({ |
| 757 | data: task, | |
| 758 | callback: typeof callback === 'function' ? callback : null | |
| 759 | }); | |
| 760 | 0 | if (cargo.saturated && tasks.length === payload) { |
| 761 | 0 | cargo.saturated(); |
| 762 | } | |
| 763 | }); | |
| 764 | 0 | async.setImmediate(cargo.process); |
| 765 | }, | |
| 766 | process: function process() { | |
| 767 | 0 | if (working) return; |
| 768 | 0 | if (tasks.length === 0) { |
| 769 | 0 | if(cargo.drain) cargo.drain(); |
| 770 | 0 | return; |
| 771 | } | |
| 772 | ||
| 773 | 0 | var ts = typeof payload === 'number' |
| 774 | ? tasks.splice(0, payload) | |
| 775 | : tasks.splice(0); | |
| 776 | ||
| 777 | 0 | var ds = _map(ts, function (task) { |
| 778 | 0 | return task.data; |
| 779 | }); | |
| 780 | ||
| 781 | 0 | if(cargo.empty) cargo.empty(); |
| 782 | 0 | working = true; |
| 783 | 0 | worker(ds, function () { |
| 784 | 0 | working = false; |
| 785 | ||
| 786 | 0 | var args = arguments; |
| 787 | 0 | _each(ts, function (data) { |
| 788 | 0 | if (data.callback) { |
| 789 | 0 | data.callback.apply(null, args); |
| 790 | } | |
| 791 | }); | |
| 792 | ||
| 793 | 0 | process(); |
| 794 | }); | |
| 795 | }, | |
| 796 | length: function () { | |
| 797 | 0 | return tasks.length; |
| 798 | }, | |
| 799 | running: function () { | |
| 800 | 0 | return working; |
| 801 | } | |
| 802 | }; | |
| 803 | 0 | return cargo; |
| 804 | }; | |
| 805 | ||
| 806 | 1 | var _console_fn = function (name) { |
| 807 | 2 | return function (fn) { |
| 808 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 809 | 0 | fn.apply(null, args.concat([function (err) { |
| 810 | 0 | var args = Array.prototype.slice.call(arguments, 1); |
| 811 | 0 | if (typeof console !== 'undefined') { |
| 812 | 0 | if (err) { |
| 813 | 0 | if (console.error) { |
| 814 | 0 | console.error(err); |
| 815 | } | |
| 816 | } | |
| 817 | 0 | else if (console[name]) { |
| 818 | 0 | _each(args, function (x) { |
| 819 | 0 | console[name](x); |
| 820 | }); | |
| 821 | } | |
| 822 | } | |
| 823 | }])); | |
| 824 | }; | |
| 825 | }; | |
| 826 | 1 | async.log = _console_fn('log'); |
| 827 | 1 | async.dir = _console_fn('dir'); |
| 828 | /*async.info = _console_fn('info'); | |
| 829 | async.warn = _console_fn('warn'); | |
| 830 | async.error = _console_fn('error');*/ | |
| 831 | ||
| 832 | 1 | async.memoize = function (fn, hasher) { |
| 833 | 0 | var memo = {}; |
| 834 | 0 | var queues = {}; |
| 835 | 0 | hasher = hasher || function (x) { |
| 836 | 0 | return x; |
| 837 | }; | |
| 838 | 0 | var memoized = function () { |
| 839 | 0 | var args = Array.prototype.slice.call(arguments); |
| 840 | 0 | var callback = args.pop(); |
| 841 | 0 | var key = hasher.apply(null, args); |
| 842 | 0 | if (key in memo) { |
| 843 | 0 | callback.apply(null, memo[key]); |
| 844 | } | |
| 845 | 0 | else if (key in queues) { |
| 846 | 0 | queues[key].push(callback); |
| 847 | } | |
| 848 | else { | |
| 849 | 0 | queues[key] = [callback]; |
| 850 | 0 | fn.apply(null, args.concat([function () { |
| 851 | 0 | memo[key] = arguments; |
| 852 | 0 | var q = queues[key]; |
| 853 | 0 | delete queues[key]; |
| 854 | 0 | for (var i = 0, l = q.length; i < l; i++) { |
| 855 | 0 | q[i].apply(null, arguments); |
| 856 | } | |
| 857 | }])); | |
| 858 | } | |
| 859 | }; | |
| 860 | 0 | memoized.memo = memo; |
| 861 | 0 | memoized.unmemoized = fn; |
| 862 | 0 | return memoized; |
| 863 | }; | |
| 864 | ||
| 865 | 1 | async.unmemoize = function (fn) { |
| 866 | 0 | return function () { |
| 867 | 0 | return (fn.unmemoized || fn).apply(null, arguments); |
| 868 | }; | |
| 869 | }; | |
| 870 | ||
| 871 | 1 | async.times = function (count, iterator, callback) { |
| 872 | 0 | var counter = []; |
| 873 | 0 | for (var i = 0; i < count; i++) { |
| 874 | 0 | counter.push(i); |
| 875 | } | |
| 876 | 0 | return async.map(counter, iterator, callback); |
| 877 | }; | |
| 878 | ||
| 879 | 1 | async.timesSeries = function (count, iterator, callback) { |
| 880 | 0 | var counter = []; |
| 881 | 0 | for (var i = 0; i < count; i++) { |
| 882 | 0 | counter.push(i); |
| 883 | } | |
| 884 | 0 | return async.mapSeries(counter, iterator, callback); |
| 885 | }; | |
| 886 | ||
| 887 | 1 | async.compose = function (/* functions... */) { |
| 888 | 0 | var fns = Array.prototype.reverse.call(arguments); |
| 889 | 0 | return function () { |
| 890 | 0 | var that = this; |
| 891 | 0 | var args = Array.prototype.slice.call(arguments); |
| 892 | 0 | var callback = args.pop(); |
| 893 | 0 | async.reduce(fns, args, function (newargs, fn, cb) { |
| 894 | 0 | fn.apply(that, newargs.concat([function () { |
| 895 | 0 | var err = arguments[0]; |
| 896 | 0 | var nextargs = Array.prototype.slice.call(arguments, 1); |
| 897 | 0 | cb(err, nextargs); |
| 898 | }])) | |
| 899 | }, | |
| 900 | function (err, results) { | |
| 901 | 0 | callback.apply(that, [err].concat(results)); |
| 902 | }); | |
| 903 | }; | |
| 904 | }; | |
| 905 | ||
| 906 | 1 | var _applyEach = function (eachfn, fns /*args...*/) { |
| 907 | 0 | var go = function () { |
| 908 | 0 | var that = this; |
| 909 | 0 | var args = Array.prototype.slice.call(arguments); |
| 910 | 0 | var callback = args.pop(); |
| 911 | 0 | return eachfn(fns, function (fn, cb) { |
| 912 | 0 | fn.apply(that, args.concat([cb])); |
| 913 | }, | |
| 914 | callback); | |
| 915 | }; | |
| 916 | 0 | if (arguments.length > 2) { |
| 917 | 0 | var args = Array.prototype.slice.call(arguments, 2); |
| 918 | 0 | return go.apply(this, args); |
| 919 | } | |
| 920 | else { | |
| 921 | 0 | return go; |
| 922 | } | |
| 923 | }; | |
| 924 | 1 | async.applyEach = doParallel(_applyEach); |
| 925 | 1 | async.applyEachSeries = doSeries(_applyEach); |
| 926 | ||
| 927 | 1 | async.forever = function (fn, callback) { |
| 928 | 0 | function next(err) { |
| 929 | 0 | if (err) { |
| 930 | 0 | if (callback) { |
| 931 | 0 | return callback(err); |
| 932 | } | |
| 933 | 0 | throw err; |
| 934 | } | |
| 935 | 0 | fn(next); |
| 936 | } | |
| 937 | 0 | next(); |
| 938 | }; | |
| 939 | ||
| 940 | // AMD / RequireJS | |
| 941 | 1 | if (typeof define !== 'undefined' && define.amd) { |
| 942 | 0 | define([], function () { |
| 943 | 0 | return async; |
| 944 | }); | |
| 945 | } | |
| 946 | // Node.js | |
| 947 | 1 | else if (typeof module !== 'undefined' && module.exports) { |
| 948 | 1 | module.exports = async; |
| 949 | } | |
| 950 | // included directly via <script> tag | |
| 951 | else { | |
| 952 | 0 | root.async = async; |
| 953 | } | |
| 954 | ||
| 955 | }()); | |
| 956 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var Agent = require('superagent').agent |
| 7 | , methods = require('methods') | |
| 8 | , http = require('http') | |
| 9 | , Test = require('./test'); | |
| 10 | ||
| 11 | /** | |
| 12 | * Expose `Agent`. | |
| 13 | */ | |
| 14 | ||
| 15 | 1 | module.exports = TestAgent; |
| 16 | ||
| 17 | /** | |
| 18 | * Initialize a new `TestAgent`. | |
| 19 | * | |
| 20 | * @param {Function|Server} app | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | function TestAgent(app){ |
| 25 | 0 | if (!(this instanceof TestAgent)) return new TestAgent(app); |
| 26 | 0 | if ('function' == typeof app) app = http.createServer(app); |
| 27 | 0 | Agent.call(this); |
| 28 | 0 | this.app = app; |
| 29 | } | |
| 30 | ||
| 31 | /** | |
| 32 | * Inherits from `Agent.prototype`. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | TestAgent.prototype.__proto__ = Agent.prototype; |
| 36 | ||
| 37 | // override HTTP verb methods | |
| 38 | ||
| 39 | 1 | methods.forEach(function(method){ |
| 40 | 24 | var name = 'delete' == method ? 'del' : method; |
| 41 | ||
| 42 | 24 | method = method.toUpperCase(); |
| 43 | 24 | TestAgent.prototype[name] = function(url, fn){ |
| 44 | 0 | var req = new Test(this.app, method, url); |
| 45 | ||
| 46 | 0 | req.on('response', this.saveCookies.bind(this)); |
| 47 | 0 | req.on('redirect', this.saveCookies.bind(this)); |
| 48 | 0 | req.on('redirect', this.attachCookies.bind(this, req)); |
| 49 | 0 | this.attachCookies(req); |
| 50 | ||
| 51 | 0 | return req; |
| 52 | }; | |
| 53 | }); |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var request = require('superagent') |
| 7 | , util = require('util') | |
| 8 | , http = require('http') | |
| 9 | , https = require('https') | |
| 10 | , assert = require('assert') | |
| 11 | , Request = request.Request; | |
| 12 | ||
| 13 | /** | |
| 14 | * Expose `Test`. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | module.exports = Test; |
| 18 | ||
| 19 | /** | |
| 20 | * Initialize a new `Test` with the given `app`, | |
| 21 | * request `method` and `path`. | |
| 22 | * | |
| 23 | * @param {Server} app | |
| 24 | * @param {String} method | |
| 25 | * @param {String} path | |
| 26 | * @api public | |
| 27 | */ | |
| 28 | ||
| 29 | 1 | function Test(app, method, path) { |
| 30 | 0 | Request.call(this, method, path); |
| 31 | 0 | this.redirects(0); |
| 32 | 0 | this.buffer(); |
| 33 | 0 | this.app = app; |
| 34 | 0 | this._fields = {}; |
| 35 | 0 | this._bodies = []; |
| 36 | 0 | this.url = 'string' == typeof app |
| 37 | ? app + path | |
| 38 | : this.serverAddress(app, path); | |
| 39 | } | |
| 40 | ||
| 41 | /** | |
| 42 | * Inherits from `Request.prototype`. | |
| 43 | */ | |
| 44 | ||
| 45 | 1 | Test.prototype.__proto__ = Request.prototype; |
| 46 | ||
| 47 | /** | |
| 48 | * Returns a URL, extracted from a server. | |
| 49 | * | |
| 50 | * @param {Server} app | |
| 51 | * @param {String} path | |
| 52 | * @returns {String} URL address | |
| 53 | * @api private | |
| 54 | */ | |
| 55 | ||
| 56 | 1 | Test.prototype.serverAddress = function(app, path){ |
| 57 | 0 | var addr = app.address(); |
| 58 | 0 | if (!addr) app.listen(0); |
| 59 | 0 | var port = app.address().port; |
| 60 | 0 | var protocol = app instanceof https.Server ? 'https' : 'http'; |
| 61 | 0 | return protocol + '://127.0.0.1:' + port + path; |
| 62 | }; | |
| 63 | ||
| 64 | /** | |
| 65 | * Expectations: | |
| 66 | * | |
| 67 | * .expect(200) | |
| 68 | * .expect(200, fn) | |
| 69 | * .expect(200, body) | |
| 70 | * .expect('Some body') | |
| 71 | * .expect('Some body', fn) | |
| 72 | * .expect('Content-Type', 'application/json') | |
| 73 | * .expect('Content-Type', 'application/json', fn) | |
| 74 | * | |
| 75 | * @return {Test} | |
| 76 | * @api public | |
| 77 | */ | |
| 78 | ||
| 79 | 1 | Test.prototype.expect = function(a, b, c){ |
| 80 | 0 | var self = this; |
| 81 | ||
| 82 | // callback | |
| 83 | 0 | if ('function' == typeof b) this.end(b); |
| 84 | 0 | if ('function' == typeof c) this.end(c); |
| 85 | ||
| 86 | // status | |
| 87 | 0 | if ('number' == typeof a) { |
| 88 | 0 | this._status = a; |
| 89 | // body | |
| 90 | 0 | if ('function' != typeof b && arguments.length > 1) this._bodies.push(b); |
| 91 | 0 | return this; |
| 92 | } | |
| 93 | ||
| 94 | // header field | |
| 95 | 0 | if ('string' == typeof b || b instanceof RegExp) { |
| 96 | 0 | if (!this._fields[a]) this._fields[a] = []; |
| 97 | 0 | this._fields[a].push(b); |
| 98 | 0 | return this; |
| 99 | } | |
| 100 | ||
| 101 | // body | |
| 102 | 0 | this._bodies.push(a); |
| 103 | ||
| 104 | 0 | return this; |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Defer invoking superagent's `.end()` until | |
| 109 | * the server is listening. | |
| 110 | * | |
| 111 | * @param {Function} fn | |
| 112 | * @api public | |
| 113 | */ | |
| 114 | ||
| 115 | 1 | Test.prototype.end = function(fn){ |
| 116 | 0 | var self = this; |
| 117 | 0 | var end = Request.prototype.end; |
| 118 | 0 | end.call(this, function(err, res){ |
| 119 | 0 | if (err) return fn(err); |
| 120 | 0 | self.assert(res, fn); |
| 121 | }); | |
| 122 | 0 | return this; |
| 123 | }; | |
| 124 | ||
| 125 | /** | |
| 126 | * Perform assertions and invoke `fn(err)`. | |
| 127 | * | |
| 128 | * @param {Response} res | |
| 129 | * @param {Function} fn | |
| 130 | * @api private | |
| 131 | */ | |
| 132 | ||
| 133 | 1 | Test.prototype.assert = function(res, fn){ |
| 134 | 0 | var status = this._status |
| 135 | , fields = this._fields | |
| 136 | , bodies = this._bodies | |
| 137 | , expecteds | |
| 138 | , actual | |
| 139 | , re; | |
| 140 | ||
| 141 | // status | |
| 142 | 0 | if (status && res.status !== status) { |
| 143 | 0 | var a = http.STATUS_CODES[status]; |
| 144 | 0 | var b = http.STATUS_CODES[res.status]; |
| 145 | 0 | return fn(new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"'), res); |
| 146 | } | |
| 147 | ||
| 148 | // body | |
| 149 | 0 | for (var i = 0; i < bodies.length; i++) { |
| 150 | 0 | var body = bodies[i]; |
| 151 | 0 | var isregexp = body instanceof RegExp; |
| 152 | // parsed | |
| 153 | 0 | if ('object' == typeof body && !isregexp) { |
| 154 | 0 | try { |
| 155 | 0 | assert.deepEqual(body, res.body); |
| 156 | } catch (err) { | |
| 157 | 0 | var a = util.inspect(body); |
| 158 | 0 | var b = util.inspect(res.body); |
| 159 | 0 | return fn(error('expected ' + a + ' response body, got ' + b, body, res.body)); |
| 160 | } | |
| 161 | } else { | |
| 162 | // string | |
| 163 | 0 | if (body !== res.text) { |
| 164 | 0 | var a = util.inspect(body); |
| 165 | 0 | var b = util.inspect(res.text); |
| 166 | ||
| 167 | // regexp | |
| 168 | 0 | if (isregexp) { |
| 169 | 0 | if (!body.test(res.text)) { |
| 170 | 0 | return fn(error('expected body ' + b + ' to match ' + body, body, res.body)); |
| 171 | } | |
| 172 | } else { | |
| 173 | 0 | return fn(error('expected ' + a + ' response body, got ' + b, body, res.body)); |
| 174 | } | |
| 175 | } | |
| 176 | } | |
| 177 | } | |
| 178 | ||
| 179 | // fields | |
| 180 | 0 | for (var field in fields) { |
| 181 | 0 | expecteds = fields[field]; |
| 182 | 0 | actual = res.header[field.toLowerCase()]; |
| 183 | 0 | if (null == actual) return fn(new Error('expected "' + field + '" header field')); |
| 184 | 0 | for (var i = 0; i < expecteds.length; i++) { |
| 185 | 0 | var fieldExpected = expecteds[i]; |
| 186 | 0 | if (fieldExpected == actual) continue; |
| 187 | 0 | if (fieldExpected instanceof RegExp) re = fieldExpected; |
| 188 | 0 | if (re && re.test(actual)) continue; |
| 189 | 0 | if (re) return fn(new Error('expected "' + field + '" matching ' + fieldExpected + ', got "' + actual + '"')); |
| 190 | 0 | return fn(new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"')); |
| 191 | } | |
| 192 | } | |
| 193 | ||
| 194 | 0 | fn.call(this, null, res); |
| 195 | }; | |
| 196 | ||
| 197 | /** | |
| 198 | * Return an `Error` with `msg` and results properties. | |
| 199 | * | |
| 200 | * @param {String} msg | |
| 201 | * @param {Mixed} expected | |
| 202 | * @param {Mixed} actual | |
| 203 | * @return {Error} | |
| 204 | * @api private | |
| 205 | */ | |
| 206 | ||
| 207 | 1 | function error(msg, expected, actual) { |
| 208 | 0 | var err = new Error(msg); |
| 209 | 0 | err.expected = expected; |
| 210 | 0 | err.actual = actual; |
| 211 | 0 | err.showDiff = true; |
| 212 | 0 | return err; |
| 213 | } | |
| 214 | ||
| 215 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var CookieJar = require('cookiejar').CookieJar; |
| 7 | 1 | var CookieAccess = require('cookiejar').CookieAccessInfo; |
| 8 | 1 | var parse = require('url').parse; |
| 9 | 1 | var request = require('./index'); |
| 10 | 1 | var methods = require('methods'); |
| 11 | ||
| 12 | /** | |
| 13 | * Expose `Agent`. | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | module.exports = Agent; |
| 17 | ||
| 18 | /** | |
| 19 | * Initialize a new `Agent`. | |
| 20 | * | |
| 21 | * @api public | |
| 22 | */ | |
| 23 | ||
| 24 | 1 | function Agent() { |
| 25 | 0 | if (!(this instanceof Agent)) return new Agent; |
| 26 | 0 | this.jar = new CookieJar; |
| 27 | } | |
| 28 | ||
| 29 | /** | |
| 30 | * Save the cookies in the given `res` to | |
| 31 | * the agent's cookie jar for persistence. | |
| 32 | * | |
| 33 | * @param {Response} res | |
| 34 | * @api private | |
| 35 | */ | |
| 36 | ||
| 37 | 1 | Agent.prototype.saveCookies = function(res){ |
| 38 | 0 | var cookies = res.headers['set-cookie']; |
| 39 | 0 | if (cookies) this.jar.setCookies(cookies); |
| 40 | }; | |
| 41 | ||
| 42 | /** | |
| 43 | * Attach cookies when available to the given `req`. | |
| 44 | * | |
| 45 | * @param {Request} req | |
| 46 | * @api private | |
| 47 | */ | |
| 48 | ||
| 49 | 1 | Agent.prototype.attachCookies = function(req){ |
| 50 | 0 | var url = parse(req.url); |
| 51 | 0 | var access = CookieAccess(url.host, url.pathname, 'https:' == url.protocol); |
| 52 | 0 | var cookies = this.jar.getCookies(access).toValueString(); |
| 53 | 0 | req.cookies = cookies; |
| 54 | }; | |
| 55 | ||
| 56 | // generate HTTP verb methods | |
| 57 | ||
| 58 | 1 | methods.forEach(function(method){ |
| 59 | 23 | var name = 'delete' == method ? 'del' : method; |
| 60 | ||
| 61 | 23 | method = method.toUpperCase(); |
| 62 | 23 | Agent.prototype[name] = function(url, fn){ |
| 63 | 0 | var req = request(method, url); |
| 64 | ||
| 65 | 0 | req.on('response', this.saveCookies.bind(this)); |
| 66 | 0 | req.on('redirect', this.saveCookies.bind(this)); |
| 67 | 0 | req.on('redirect', this.attachCookies.bind(this, req)); |
| 68 | 0 | this.attachCookies(req); |
| 69 | ||
| 70 | 0 | fn && req.end(fn); |
| 71 | 0 | return req; |
| 72 | }; | |
| 73 | }); | |
| 74 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var debug = require('debug')('superagent'); |
| 7 | 1 | var formidable = require('formidable'); |
| 8 | 1 | var Response = require('./response'); |
| 9 | 1 | var parse = require('url').parse; |
| 10 | 1 | var format = require('url').format; |
| 11 | 1 | var methods = require('methods'); |
| 12 | 1 | var Stream = require('stream'); |
| 13 | 1 | var utils = require('./utils'); |
| 14 | 1 | var Part = require('./part'); |
| 15 | 1 | var mime = require('mime'); |
| 16 | 1 | var https = require('https'); |
| 17 | 1 | var http = require('http'); |
| 18 | 1 | var fs = require('fs'); |
| 19 | 1 | var qs = require('qs'); |
| 20 | 1 | var zlib = require('zlib'); |
| 21 | 1 | var util = require('util'); |
| 22 | ||
| 23 | /** | |
| 24 | * Expose the request function. | |
| 25 | */ | |
| 26 | ||
| 27 | 1 | exports = module.exports = request; |
| 28 | ||
| 29 | /** | |
| 30 | * Expose the agent function | |
| 31 | */ | |
| 32 | ||
| 33 | 1 | exports.agent = require('./agent'); |
| 34 | ||
| 35 | ||
| 36 | /** | |
| 37 | * Expose `Part`. | |
| 38 | */ | |
| 39 | ||
| 40 | 1 | exports.Part = Part; |
| 41 | ||
| 42 | /** | |
| 43 | * Noop. | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | function noop(){}; |
| 47 | ||
| 48 | /** | |
| 49 | * Expose `Response`. | |
| 50 | */ | |
| 51 | ||
| 52 | 1 | exports.Response = Response; |
| 53 | ||
| 54 | /** | |
| 55 | * Define "form" mime type. | |
| 56 | */ | |
| 57 | ||
| 58 | 1 | mime.define({ |
| 59 | 'application/x-www-form-urlencoded': ['form', 'urlencoded', 'form-data'] | |
| 60 | }); | |
| 61 | ||
| 62 | /** | |
| 63 | * Protocol map. | |
| 64 | */ | |
| 65 | ||
| 66 | 1 | exports.protocols = { |
| 67 | 'http:': http, | |
| 68 | 'https:': https | |
| 69 | }; | |
| 70 | ||
| 71 | /** | |
| 72 | * Check if `obj` is an object. | |
| 73 | * | |
| 74 | * @param {Object} obj | |
| 75 | * @return {Boolean} | |
| 76 | * @api private | |
| 77 | */ | |
| 78 | ||
| 79 | 1 | function isObject(obj) { |
| 80 | 0 | return null != obj && 'object' == typeof obj; |
| 81 | } | |
| 82 | ||
| 83 | /** | |
| 84 | * Default serialization map. | |
| 85 | * | |
| 86 | * superagent.serialize['application/xml'] = function(obj){ | |
| 87 | * return 'generated xml here'; | |
| 88 | * }; | |
| 89 | * | |
| 90 | */ | |
| 91 | ||
| 92 | 1 | exports.serialize = { |
| 93 | 'application/x-www-form-urlencoded': qs.stringify, | |
| 94 | 'application/json': JSON.stringify | |
| 95 | }; | |
| 96 | ||
| 97 | /** | |
| 98 | * Default parsers. | |
| 99 | * | |
| 100 | * superagent.parse['application/xml'] = function(res, fn){ | |
| 101 | * fn(null, result); | |
| 102 | * }; | |
| 103 | * | |
| 104 | */ | |
| 105 | ||
| 106 | 1 | exports.parse = require('./parsers'); |
| 107 | ||
| 108 | /** | |
| 109 | * Initialize a new `Request` with the given `method` and `url`. | |
| 110 | * | |
| 111 | * @param {String} method | |
| 112 | * @param {String|Object} url | |
| 113 | * @api public | |
| 114 | */ | |
| 115 | ||
| 116 | 1 | function Request(method, url) { |
| 117 | 0 | var self = this; |
| 118 | 0 | if ('string' != typeof url) url = format(url); |
| 119 | 0 | this._agent = false; |
| 120 | 0 | this.method = method; |
| 121 | 0 | this.url = url; |
| 122 | 0 | this.header = {}; |
| 123 | 0 | this.writable = true; |
| 124 | 0 | this._redirects = 0; |
| 125 | 0 | this.redirects(5); |
| 126 | 0 | this.attachments = []; |
| 127 | 0 | this.cookies = ''; |
| 128 | 0 | this._redirectList = []; |
| 129 | 0 | this.on('end', this.clearTimeout.bind(this)); |
| 130 | 0 | this.on('response', function(res){ |
| 131 | 0 | self.callback(null, res); |
| 132 | }); | |
| 133 | } | |
| 134 | ||
| 135 | /** | |
| 136 | * Inherit from `Stream.prototype`. | |
| 137 | */ | |
| 138 | ||
| 139 | 1 | Request.prototype.__proto__ = Stream.prototype; |
| 140 | ||
| 141 | /** | |
| 142 | * Queue the given `file` as an attachment | |
| 143 | * with optional `filename`. | |
| 144 | * | |
| 145 | * @param {String} field | |
| 146 | * @param {String} file | |
| 147 | * @param {String} filename | |
| 148 | * @return {Request} for chaining | |
| 149 | * @api public | |
| 150 | */ | |
| 151 | ||
| 152 | 1 | Request.prototype.attach = function(field, file, filename){ |
| 153 | 0 | debug('attach %s %s', field, file); |
| 154 | 0 | this.attachments.push({ |
| 155 | field: field, | |
| 156 | path: file, | |
| 157 | part: new Part(this), | |
| 158 | filename: filename || file | |
| 159 | }); | |
| 160 | 0 | return this; |
| 161 | }; | |
| 162 | ||
| 163 | /** | |
| 164 | * Set the max redirects to `n`. | |
| 165 | * | |
| 166 | * @param {Number} n | |
| 167 | * @return {Request} for chaining | |
| 168 | * @api public | |
| 169 | */ | |
| 170 | ||
| 171 | 1 | Request.prototype.redirects = function(n){ |
| 172 | 0 | debug('max redirects %s', n); |
| 173 | 0 | this._maxRedirects = n; |
| 174 | 0 | return this; |
| 175 | }; | |
| 176 | ||
| 177 | /** | |
| 178 | * Return a new `Part` for this request. | |
| 179 | * | |
| 180 | * @return {Part} | |
| 181 | * @api public | |
| 182 | */ | |
| 183 | ||
| 184 | 1 | Request.prototype.part = function(){ |
| 185 | 0 | return new Part(this); |
| 186 | }; | |
| 187 | ||
| 188 | /** | |
| 189 | * Gets/sets the `Agent` to use for this HTTP request. The default (if this | |
| 190 | * function is not called) is to opt out of connection pooling (`agent: false`). | |
| 191 | * | |
| 192 | * @param {http.Agent} agent | |
| 193 | * @return {http.Agent} | |
| 194 | * @api public | |
| 195 | */ | |
| 196 | ||
| 197 | 1 | Request.prototype.agent = function(agent){ |
| 198 | 0 | if (agent) this._agent = agent; |
| 199 | 0 | return this._agent; |
| 200 | }; | |
| 201 | ||
| 202 | /** | |
| 203 | * Set header `field` to `val`, or multiple fields with one object. | |
| 204 | * | |
| 205 | * Examples: | |
| 206 | * | |
| 207 | * req.get('/') | |
| 208 | * .set('Accept', 'application/json') | |
| 209 | * .set('X-API-Key', 'foobar') | |
| 210 | * .end(callback); | |
| 211 | * | |
| 212 | * req.get('/') | |
| 213 | * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) | |
| 214 | * .end(callback); | |
| 215 | * | |
| 216 | * @param {String|Object} field | |
| 217 | * @param {String} val | |
| 218 | * @return {Request} for chaining | |
| 219 | * @api public | |
| 220 | */ | |
| 221 | ||
| 222 | 1 | Request.prototype.set = function(field, val){ |
| 223 | 0 | if (isObject(field)) { |
| 224 | 0 | for (var key in field) { |
| 225 | 0 | this.set(key, field[key]); |
| 226 | } | |
| 227 | 0 | return this; |
| 228 | } | |
| 229 | ||
| 230 | 0 | debug('set %s "%s"', field, val); |
| 231 | 0 | this.request().setHeader(field, val); |
| 232 | 0 | return this; |
| 233 | }; | |
| 234 | ||
| 235 | /** | |
| 236 | * Get request header `field`. | |
| 237 | * | |
| 238 | * @param {String} field | |
| 239 | * @return {String} | |
| 240 | * @api public | |
| 241 | */ | |
| 242 | ||
| 243 | 1 | Request.prototype.get = function(field){ |
| 244 | 0 | return this.request().getHeader(field); |
| 245 | }; | |
| 246 | ||
| 247 | /** | |
| 248 | * Set _Content-Type_ response header passed through `mime.lookup()`. | |
| 249 | * | |
| 250 | * Examples: | |
| 251 | * | |
| 252 | * request.post('/') | |
| 253 | * .type('xml') | |
| 254 | * .send(xmlstring) | |
| 255 | * .end(callback); | |
| 256 | * | |
| 257 | * request.post('/') | |
| 258 | * .type('json') | |
| 259 | * .send(jsonstring) | |
| 260 | * .end(callback); | |
| 261 | * | |
| 262 | * request.post('/') | |
| 263 | * .type('application/json') | |
| 264 | * .send(jsonstring) | |
| 265 | * .end(callback); | |
| 266 | * | |
| 267 | * @param {String} type | |
| 268 | * @return {Request} for chaining | |
| 269 | * @api public | |
| 270 | */ | |
| 271 | ||
| 272 | 1 | Request.prototype.type = function(type){ |
| 273 | 0 | return this.set('Content-Type', ~type.indexOf('/') |
| 274 | ? type | |
| 275 | : mime.lookup(type)); | |
| 276 | }; | |
| 277 | ||
| 278 | /** | |
| 279 | * Set _Accept_ response header passed through `mime.lookup()`. | |
| 280 | * | |
| 281 | * Examples: | |
| 282 | * | |
| 283 | * superagent.types.json = 'application/json'; | |
| 284 | * | |
| 285 | * request.get('/agent') | |
| 286 | * .accept('json') | |
| 287 | * .end(callback); | |
| 288 | * | |
| 289 | * request.get('/agent') | |
| 290 | * .accept('application/json') | |
| 291 | * .end(callback); | |
| 292 | * | |
| 293 | * @param {String} accept | |
| 294 | * @return {Request} for chaining | |
| 295 | * @api public | |
| 296 | */ | |
| 297 | ||
| 298 | 1 | Request.prototype.accept = function(type){ |
| 299 | 0 | return this.set('Accept', ~type.indexOf('/') |
| 300 | ? type | |
| 301 | : mime.lookup(type)); | |
| 302 | }; | |
| 303 | ||
| 304 | /** | |
| 305 | * Add query-string `val`. | |
| 306 | * | |
| 307 | * Examples: | |
| 308 | * | |
| 309 | * request.get('/shoes') | |
| 310 | * .query('size=10') | |
| 311 | * .query({ color: 'blue' }) | |
| 312 | * | |
| 313 | * @param {Object|String} val | |
| 314 | * @return {Request} for chaining | |
| 315 | * @api public | |
| 316 | */ | |
| 317 | ||
| 318 | 1 | Request.prototype.query = function(val){ |
| 319 | 0 | var req = this.request(); |
| 320 | 0 | if ('string' != typeof val) val = qs.stringify(val); |
| 321 | 0 | if (!val.length) return this; |
| 322 | 0 | debug('query %s', val); |
| 323 | 0 | req.path += (~req.path.indexOf('?') ? '&' : '?') + val; |
| 324 | 0 | return this; |
| 325 | }; | |
| 326 | ||
| 327 | /** | |
| 328 | * Send `data`, defaulting the `.type()` to "json" when | |
| 329 | * an object is given. | |
| 330 | * | |
| 331 | * Examples: | |
| 332 | * | |
| 333 | * // manual json | |
| 334 | * request.post('/user') | |
| 335 | * .type('json') | |
| 336 | * .send('{"name":"tj"}') | |
| 337 | * .end(callback) | |
| 338 | * | |
| 339 | * // auto json | |
| 340 | * request.post('/user') | |
| 341 | * .send({ name: 'tj' }) | |
| 342 | * .end(callback) | |
| 343 | * | |
| 344 | * // manual x-www-form-urlencoded | |
| 345 | * request.post('/user') | |
| 346 | * .type('form') | |
| 347 | * .send('name=tj') | |
| 348 | * .end(callback) | |
| 349 | * | |
| 350 | * // auto x-www-form-urlencoded | |
| 351 | * request.post('/user') | |
| 352 | * .type('form') | |
| 353 | * .send({ name: 'tj' }) | |
| 354 | * .end(callback) | |
| 355 | * | |
| 356 | * // string defaults to x-www-form-urlencoded | |
| 357 | * request.post('/user') | |
| 358 | * .send('name=tj') | |
| 359 | * .send('foo=bar') | |
| 360 | * .send('bar=baz') | |
| 361 | * .end(callback) | |
| 362 | * | |
| 363 | * @param {String|Object} data | |
| 364 | * @return {Request} for chaining | |
| 365 | * @api public | |
| 366 | */ | |
| 367 | ||
| 368 | 1 | Request.prototype.send = function(data){ |
| 369 | 0 | var obj = isObject(data); |
| 370 | 0 | var req = this.request(); |
| 371 | 0 | var type = req.getHeader('Content-Type'); |
| 372 | ||
| 373 | // merge | |
| 374 | 0 | if (obj && isObject(this._data)) { |
| 375 | 0 | for (var key in data) { |
| 376 | 0 | this._data[key] = data[key]; |
| 377 | } | |
| 378 | // string | |
| 379 | 0 | } else if ('string' == typeof data) { |
| 380 | // default to x-www-form-urlencoded | |
| 381 | 0 | if (!type) this.type('form'); |
| 382 | 0 | type = req.getHeader('Content-Type'); |
| 383 | ||
| 384 | // concat & | |
| 385 | 0 | if ('application/x-www-form-urlencoded' == type) { |
| 386 | 0 | this._data = this._data |
| 387 | ? this._data + '&' + data | |
| 388 | : data; | |
| 389 | } else { | |
| 390 | 0 | this._data = (this._data || '') + data; |
| 391 | } | |
| 392 | } else { | |
| 393 | 0 | this._data = data; |
| 394 | } | |
| 395 | ||
| 396 | 0 | if (!obj) return this; |
| 397 | ||
| 398 | // default to json | |
| 399 | 0 | if (!type) this.type('json'); |
| 400 | 0 | return this; |
| 401 | }; | |
| 402 | ||
| 403 | /** | |
| 404 | * Write raw `data` / `encoding` to the socket. | |
| 405 | * | |
| 406 | * @param {Buffer|String} data | |
| 407 | * @param {String} encoding | |
| 408 | * @return {Boolean} | |
| 409 | * @api public | |
| 410 | */ | |
| 411 | ||
| 412 | 1 | Request.prototype.write = function(data, encoding){ |
| 413 | 0 | return this.request().write(data, encoding); |
| 414 | }; | |
| 415 | ||
| 416 | /** | |
| 417 | * Pipe the request body to `stream`. | |
| 418 | * | |
| 419 | * @param {Stream} stream | |
| 420 | * @param {Object} options | |
| 421 | * @return {Stream} | |
| 422 | * @api public | |
| 423 | */ | |
| 424 | ||
| 425 | 1 | Request.prototype.pipe = function(stream, options){ |
| 426 | 0 | this.piped = true; // HACK... |
| 427 | 0 | this.buffer(false); |
| 428 | 0 | this.end().req.on('response', function(res){ |
| 429 | 0 | if (/^(deflate|gzip)$/.test(res.headers['content-encoding'])) { |
| 430 | 0 | res.pipe(zlib.createUnzip()).pipe(stream, options); |
| 431 | } else { | |
| 432 | 0 | res.pipe(stream, options); |
| 433 | } | |
| 434 | }); | |
| 435 | 0 | return stream; |
| 436 | }; | |
| 437 | ||
| 438 | /** | |
| 439 | * Enable / disable buffering. | |
| 440 | * | |
| 441 | * @return {Boolean} [val] | |
| 442 | * @return {Request} for chaining | |
| 443 | * @api public | |
| 444 | */ | |
| 445 | ||
| 446 | 1 | Request.prototype.buffer = function(val){ |
| 447 | 0 | this._buffer = false === val |
| 448 | ? false | |
| 449 | : true; | |
| 450 | 0 | return this; |
| 451 | }; | |
| 452 | ||
| 453 | /** | |
| 454 | * Set timeout to `ms`. | |
| 455 | * | |
| 456 | * @param {Number} ms | |
| 457 | * @return {Request} for chaining | |
| 458 | * @api public | |
| 459 | */ | |
| 460 | ||
| 461 | 1 | Request.prototype.timeout = function(ms){ |
| 462 | 0 | this._timeout = ms; |
| 463 | 0 | return this; |
| 464 | }; | |
| 465 | ||
| 466 | /** | |
| 467 | * Clear previous timeout. | |
| 468 | * | |
| 469 | * @return {Request} for chaining | |
| 470 | * @api public | |
| 471 | */ | |
| 472 | ||
| 473 | 1 | Request.prototype.clearTimeout = function(){ |
| 474 | 0 | debug('clear timeout %s %s', this.method, this.url); |
| 475 | 0 | this._timeout = 0; |
| 476 | 0 | clearTimeout(this._timer); |
| 477 | 0 | return this; |
| 478 | }; | |
| 479 | ||
| 480 | /** | |
| 481 | * Abort and clear timeout. | |
| 482 | * | |
| 483 | * @api public | |
| 484 | */ | |
| 485 | ||
| 486 | 1 | Request.prototype.abort = function(){ |
| 487 | 0 | debug('abort %s %s', this.method, this.url); |
| 488 | 0 | this._aborted = true; |
| 489 | 0 | this.clearTimeout(); |
| 490 | 0 | this.req.abort(); |
| 491 | }; | |
| 492 | ||
| 493 | /** | |
| 494 | * Define the parser to be used for this response. | |
| 495 | * | |
| 496 | * @param {Function} fn | |
| 497 | * @return {Request} for chaining | |
| 498 | * @api public | |
| 499 | */ | |
| 500 | ||
| 501 | 1 | Request.prototype.parse = function(fn){ |
| 502 | 0 | this._parser = fn; |
| 503 | 0 | return this; |
| 504 | }; | |
| 505 | ||
| 506 | /** | |
| 507 | * Redirect to `url | |
| 508 | * | |
| 509 | * @param {IncomingMessage} res | |
| 510 | * @return {Request} for chaining | |
| 511 | * @api private | |
| 512 | */ | |
| 513 | ||
| 514 | 1 | Request.prototype.redirect = function(res){ |
| 515 | 0 | var url = res.headers.location; |
| 516 | 0 | debug('redirect %s -> %s', this.url, url); |
| 517 | ||
| 518 | // location | |
| 519 | 0 | if (!~url.indexOf('://')) { |
| 520 | 0 | if (0 != url.indexOf('//')) { |
| 521 | 0 | url = '//' + this.host + url; |
| 522 | } | |
| 523 | 0 | url = this.protocol + url; |
| 524 | } | |
| 525 | ||
| 526 | // ensure the response is being consumed | |
| 527 | // this is required for Node v0.10+ | |
| 528 | 0 | res.resume(); |
| 529 | ||
| 530 | // strip Content-* related fields | |
| 531 | // in case of POST etc | |
| 532 | 0 | var header = utils.cleanHeader(this.req._headers); |
| 533 | 0 | delete this.req; |
| 534 | ||
| 535 | // force GET | |
| 536 | 0 | this.method = 'HEAD' == this.method |
| 537 | ? 'HEAD' | |
| 538 | : 'GET'; | |
| 539 | ||
| 540 | // redirect | |
| 541 | 0 | this._data = null; |
| 542 | 0 | this.url = url; |
| 543 | 0 | this._redirectList.push(url); |
| 544 | 0 | this.clearTimeout(); |
| 545 | 0 | this.emit('redirect', res); |
| 546 | 0 | this.set(header); |
| 547 | 0 | this.end(this._callback); |
| 548 | 0 | return this; |
| 549 | }; | |
| 550 | ||
| 551 | /** | |
| 552 | * Set Authorization field value with `user` and `pass`. | |
| 553 | * | |
| 554 | * @param {String} user | |
| 555 | * @param {String} pass | |
| 556 | * @return {Request} for chaining | |
| 557 | * @api public | |
| 558 | */ | |
| 559 | ||
| 560 | 1 | Request.prototype.auth = function(user, pass){ |
| 561 | 0 | var str = new Buffer(user + ':' + pass).toString('base64'); |
| 562 | 0 | return this.set('Authorization', 'Basic ' + str); |
| 563 | }; | |
| 564 | ||
| 565 | /** | |
| 566 | * Write the field `name` and `val`. | |
| 567 | * | |
| 568 | * @param {String} name | |
| 569 | * @param {String} val | |
| 570 | * @return {Request} for chaining | |
| 571 | * @api public | |
| 572 | */ | |
| 573 | ||
| 574 | 1 | Request.prototype.field = function(name, val){ |
| 575 | 0 | this.part().name(name).write(String(val)); |
| 576 | 0 | return this; |
| 577 | }; | |
| 578 | ||
| 579 | /** | |
| 580 | * Return an http[s] request. | |
| 581 | * | |
| 582 | * @return {OutgoingMessage} | |
| 583 | * @api private | |
| 584 | */ | |
| 585 | ||
| 586 | 1 | Request.prototype.request = function(){ |
| 587 | 0 | if (this.req) return this.req; |
| 588 | ||
| 589 | 0 | var self = this; |
| 590 | 0 | var options = {}; |
| 591 | 0 | var data = this._data; |
| 592 | 0 | var url = this.url; |
| 593 | ||
| 594 | // default to http:// | |
| 595 | 0 | if (0 != url.indexOf('http')) url = 'http://' + url; |
| 596 | 0 | url = parse(url, true); |
| 597 | ||
| 598 | // options | |
| 599 | 0 | options.method = this.method; |
| 600 | 0 | options.port = url.port; |
| 601 | 0 | options.path = url.pathname; |
| 602 | 0 | options.host = url.hostname; |
| 603 | 0 | options.agent = this._agent; |
| 604 | ||
| 605 | // initiate request | |
| 606 | 0 | var mod = exports.protocols[url.protocol]; |
| 607 | ||
| 608 | // request | |
| 609 | 0 | var req = this.req = mod.request(options); |
| 610 | 0 | if ('HEAD' != options.method) req.setHeader('Accept-Encoding', 'gzip, deflate'); |
| 611 | 0 | this.protocol = url.protocol; |
| 612 | 0 | this.host = url.host; |
| 613 | ||
| 614 | // expose events | |
| 615 | 0 | req.on('drain', function(){ self.emit('drain'); }); |
| 616 | ||
| 617 | 0 | req.on('error', function(err){ |
| 618 | // flag abortion here for out timeouts | |
| 619 | // because node will emit a faux-error "socket hang up" | |
| 620 | // when request is aborted before a connection is made | |
| 621 | 0 | if (self._aborted) return; |
| 622 | 0 | self.callback(err); |
| 623 | }); | |
| 624 | ||
| 625 | // auth | |
| 626 | 0 | if (url.auth) { |
| 627 | 0 | var auth = url.auth.split(':'); |
| 628 | 0 | this.auth(auth[0], auth[1]); |
| 629 | } | |
| 630 | ||
| 631 | // query | |
| 632 | 0 | this.query(url.query); |
| 633 | ||
| 634 | // add cookies | |
| 635 | 0 | req.setHeader('Cookie', this.cookies); |
| 636 | ||
| 637 | 0 | return req; |
| 638 | }; | |
| 639 | ||
| 640 | /** | |
| 641 | * Invoke the callback with `err` and `res` | |
| 642 | * and handle arity check. | |
| 643 | * | |
| 644 | * @param {Error} err | |
| 645 | * @param {Response} res | |
| 646 | * @api private | |
| 647 | */ | |
| 648 | ||
| 649 | 1 | Request.prototype.callback = function(err, res){ |
| 650 | 0 | var fn = this._callback; |
| 651 | 0 | this.clearTimeout(); |
| 652 | 0 | if (this.called) return console.warn('double callback!'); |
| 653 | 0 | this.called = true; |
| 654 | 0 | if (2 == fn.length) return fn(err, res); |
| 655 | 0 | if (err) return this.emit('error', err); |
| 656 | 0 | fn(res); |
| 657 | }; | |
| 658 | ||
| 659 | /** | |
| 660 | * Initiate request, invoking callback `fn(err, res)` | |
| 661 | * with an instanceof `Response`. | |
| 662 | * | |
| 663 | * @param {Function} fn | |
| 664 | * @return {Request} for chaining | |
| 665 | * @api public | |
| 666 | */ | |
| 667 | ||
| 668 | 1 | Request.prototype.end = function(fn){ |
| 669 | 0 | var self = this; |
| 670 | 0 | var data = this._data; |
| 671 | 0 | var req = this.request(); |
| 672 | 0 | var buffer = this._buffer; |
| 673 | 0 | var method = this.method; |
| 674 | 0 | var timeout = this._timeout; |
| 675 | 0 | debug('%s %s', this.method, this.url); |
| 676 | ||
| 677 | // store callback | |
| 678 | 0 | this._callback = fn || noop; |
| 679 | ||
| 680 | // timeout | |
| 681 | 0 | if (timeout && !this._timer) { |
| 682 | 0 | debug('timeout %sms %s %s', timeout, this.method, this.url); |
| 683 | 0 | this._timer = setTimeout(function(){ |
| 684 | 0 | var err = new Error('timeout of ' + timeout + 'ms exceeded'); |
| 685 | 0 | err.timeout = timeout; |
| 686 | 0 | self.abort(); |
| 687 | 0 | self.callback(err); |
| 688 | }, timeout); | |
| 689 | } | |
| 690 | ||
| 691 | // body | |
| 692 | 0 | if ('HEAD' != method && !req._headerSent) { |
| 693 | // serialize stuff | |
| 694 | 0 | if ('string' != typeof data) { |
| 695 | 0 | var contentType = req.getHeader('Content-Type') |
| 696 | // Parse out just the content type from the header (ignore the charset) | |
| 697 | 0 | if (contentType) contentType = contentType.split(';')[0] |
| 698 | 0 | var serialize = exports.serialize[contentType]; |
| 699 | 0 | if (serialize) data = serialize(data); |
| 700 | } | |
| 701 | ||
| 702 | // content-length | |
| 703 | 0 | if (data && !req.getHeader('Content-Length')) { |
| 704 | 0 | this.set('Content-Length', Buffer.byteLength(data)); |
| 705 | } | |
| 706 | } | |
| 707 | ||
| 708 | // response | |
| 709 | 0 | req.on('response', function(res){ |
| 710 | 0 | debug('%s %s -> %s', self.method, self.url, res.statusCode); |
| 711 | 0 | var max = self._maxRedirects; |
| 712 | 0 | var mime = utils.type(res.headers['content-type'] || ''); |
| 713 | 0 | var len = res.headers['content-length']; |
| 714 | 0 | var type = mime.split('/'); |
| 715 | 0 | var subtype = type[1]; |
| 716 | 0 | var type = type[0]; |
| 717 | 0 | var multipart = 'multipart' == type; |
| 718 | 0 | var redirect = isRedirect(res.statusCode); |
| 719 | ||
| 720 | 0 | if (self.piped) { |
| 721 | 0 | res.on('end', function(){ |
| 722 | 0 | self.emit('end'); |
| 723 | }); | |
| 724 | 0 | return; |
| 725 | } | |
| 726 | ||
| 727 | // redirect | |
| 728 | 0 | if (redirect && self._redirects++ != max) { |
| 729 | 0 | return self.redirect(res); |
| 730 | } | |
| 731 | ||
| 732 | // zlib support | |
| 733 | 0 | if (/^(deflate|gzip)$/.test(res.headers['content-encoding'])) { |
| 734 | 0 | utils.unzip(req, res); |
| 735 | } | |
| 736 | ||
| 737 | // don't buffer multipart | |
| 738 | 0 | if (multipart) buffer = false; |
| 739 | ||
| 740 | // TODO: make all parsers take callbacks | |
| 741 | 0 | if (multipart) { |
| 742 | 0 | var form = new formidable.IncomingForm; |
| 743 | ||
| 744 | 0 | form.parse(res, function(err, fields, files){ |
| 745 | 0 | if (err) return self.callback(err); |
| 746 | 0 | var response = new Response(req, res); |
| 747 | 0 | response.body = fields; |
| 748 | 0 | response.files = files; |
| 749 | 0 | response.redirects = self._redirectList; |
| 750 | 0 | self.emit('end'); |
| 751 | 0 | self.callback(null, response); |
| 752 | }); | |
| 753 | 0 | return; |
| 754 | } | |
| 755 | ||
| 756 | // by default only buffer text/*, json | |
| 757 | // and messed up thing from hell | |
| 758 | 0 | var text = isText(mime); |
| 759 | 0 | if (null == buffer && text) buffer = true; |
| 760 | ||
| 761 | // parser | |
| 762 | 0 | var parse = 'text' == type |
| 763 | ? exports.parse.text | |
| 764 | : exports.parse[mime]; | |
| 765 | ||
| 766 | // buffered response | |
| 767 | 0 | if (buffer) parse = parse || exports.parse.text; |
| 768 | ||
| 769 | // explicit parser | |
| 770 | 0 | if (self._parser) parse = self._parser; |
| 771 | ||
| 772 | // parse | |
| 773 | 0 | if (parse) { |
| 774 | 0 | parse(res, function(err, obj){ |
| 775 | // TODO: handle error | |
| 776 | 0 | res.body = obj; |
| 777 | }); | |
| 778 | } | |
| 779 | ||
| 780 | // unbuffered | |
| 781 | 0 | if (!buffer) { |
| 782 | 0 | debug('unbuffered %s %s', self.method, self.url); |
| 783 | 0 | self.res = res; |
| 784 | 0 | var response = new Response(self.req, self.res); |
| 785 | 0 | response.redirects = self._redirectList; |
| 786 | 0 | self.emit('response', response); |
| 787 | 0 | if (multipart) return // allow multipart to handle end event |
| 788 | 0 | res.on('end', function(){ |
| 789 | 0 | debug('end %s %s', self.method, self.url); |
| 790 | 0 | self.emit('end'); |
| 791 | }) | |
| 792 | 0 | return; |
| 793 | } | |
| 794 | ||
| 795 | // end event | |
| 796 | 0 | self.res = res; |
| 797 | 0 | res.on('end', function(){ |
| 798 | 0 | debug('end %s %s', self.method, self.url); |
| 799 | // TODO: unless buffering emit earlier to stream | |
| 800 | 0 | var response = new Response(self.req, self.res); |
| 801 | 0 | response.redirects = self._redirectList; |
| 802 | 0 | self.emit('response', response); |
| 803 | 0 | self.emit('end'); |
| 804 | }); | |
| 805 | }); | |
| 806 | ||
| 807 | 0 | if (this.attachments.length) return this.writeAttachments(); |
| 808 | ||
| 809 | // multi-part boundary | |
| 810 | 0 | if (this._boundary) this.writeFinalBoundary(); |
| 811 | ||
| 812 | 0 | req.end(data); |
| 813 | 0 | return this; |
| 814 | }; | |
| 815 | ||
| 816 | /** | |
| 817 | * Write the final boundary. | |
| 818 | * | |
| 819 | * @api private | |
| 820 | */ | |
| 821 | ||
| 822 | 1 | Request.prototype.writeFinalBoundary = function(){ |
| 823 | 0 | this.request().write('\r\n--' + this._boundary + '--'); |
| 824 | }; | |
| 825 | ||
| 826 | /** | |
| 827 | * Get total bytesize of all attachments. | |
| 828 | * | |
| 829 | * @param {Function} fn | |
| 830 | * @api private | |
| 831 | */ | |
| 832 | ||
| 833 | 1 | Request.prototype.attachmentSize = function(fn){ |
| 834 | 0 | var files = this.attachments; |
| 835 | 0 | var pending = files.length; |
| 836 | 0 | var bytes = 0; |
| 837 | 0 | var self = this; |
| 838 | ||
| 839 | 0 | files.forEach(function(file){ |
| 840 | 0 | fs.stat(file.path, function(err, s){ |
| 841 | 0 | if (s) bytes += s.size; |
| 842 | 0 | --pending || fn(bytes); |
| 843 | }); | |
| 844 | }) | |
| 845 | }; | |
| 846 | ||
| 847 | /** | |
| 848 | * Write the attachments in sequence. | |
| 849 | * | |
| 850 | * @api private | |
| 851 | */ | |
| 852 | ||
| 853 | 1 | Request.prototype.writeAttachments = function(){ |
| 854 | 0 | var files = this.attachments; |
| 855 | 0 | var req = this.request(); |
| 856 | 0 | var written = 0; |
| 857 | 0 | var self = this; |
| 858 | ||
| 859 | 0 | this.attachmentSize(function(total){ |
| 860 | 0 | function next() { |
| 861 | 0 | var file = files.shift(); |
| 862 | 0 | if (!file) { |
| 863 | 0 | self.writeFinalBoundary(); |
| 864 | 0 | return req.end(); |
| 865 | } | |
| 866 | ||
| 867 | 0 | file.part.attachment(file.field, file.filename); |
| 868 | 0 | var stream = fs.createReadStream(file.path); |
| 869 | ||
| 870 | // TODO: pipe | |
| 871 | // TODO: handle errors | |
| 872 | 0 | stream.on('data', function(data){ |
| 873 | 0 | written += data.length; |
| 874 | 0 | file.part.write(data); |
| 875 | 0 | self.emit('progress', { |
| 876 | percent: written / total * 100 | 0, | |
| 877 | written: written, | |
| 878 | total: total | |
| 879 | }); | |
| 880 | }).on('error', function(err){ | |
| 881 | 0 | self.emit('error', err); |
| 882 | }).on('end', next); | |
| 883 | } | |
| 884 | ||
| 885 | 0 | next(); |
| 886 | }) | |
| 887 | }; | |
| 888 | ||
| 889 | /** | |
| 890 | * Expose `Request`. | |
| 891 | */ | |
| 892 | ||
| 893 | 1 | exports.Request = Request; |
| 894 | ||
| 895 | /** | |
| 896 | * Issue a request: | |
| 897 | * | |
| 898 | * Examples: | |
| 899 | * | |
| 900 | * request('GET', '/users').end(callback) | |
| 901 | * request('/users').end(callback) | |
| 902 | * request('/users', callback) | |
| 903 | * | |
| 904 | * @param {String} method | |
| 905 | * @param {String|Function} url or callback | |
| 906 | * @return {Request} | |
| 907 | * @api public | |
| 908 | */ | |
| 909 | ||
| 910 | 1 | function request(method, url) { |
| 911 | // callback | |
| 912 | 0 | if ('function' == typeof url) { |
| 913 | 0 | return new Request('GET', method).end(url); |
| 914 | } | |
| 915 | ||
| 916 | // url first | |
| 917 | 0 | if (1 == arguments.length) { |
| 918 | 0 | return new Request('GET', method); |
| 919 | } | |
| 920 | ||
| 921 | 0 | return new Request(method, url); |
| 922 | } | |
| 923 | ||
| 924 | // generate HTTP verb methods | |
| 925 | ||
| 926 | 1 | methods.forEach(function(method){ |
| 927 | 23 | var name = 'delete' == method ? 'del' : method; |
| 928 | 23 | method = method.toUpperCase(); |
| 929 | 23 | request[name] = function(url, fn){ |
| 930 | 0 | var req = request(method, url); |
| 931 | 0 | fn && req.end(fn); |
| 932 | 0 | return req; |
| 933 | }; | |
| 934 | }); | |
| 935 | ||
| 936 | /** | |
| 937 | * Check if `mime` is text and should be buffered. | |
| 938 | * | |
| 939 | * @param {String} mime | |
| 940 | * @return {Boolean} | |
| 941 | * @api public | |
| 942 | */ | |
| 943 | ||
| 944 | 1 | function isText(mime) { |
| 945 | 0 | var parts = mime.split('/'); |
| 946 | 0 | var type = parts[0]; |
| 947 | 0 | var subtype = parts[1]; |
| 948 | ||
| 949 | 0 | return 'text' == type |
| 950 | || 'json' == subtype | |
| 951 | || 'x-www-form-urlencoded' == subtype; | |
| 952 | } | |
| 953 | ||
| 954 | /** | |
| 955 | * Check if we should follow the redirect `code`. | |
| 956 | * | |
| 957 | * @param {Number} code | |
| 958 | * @return {Boolean} | |
| 959 | * @api private | |
| 960 | */ | |
| 961 | ||
| 962 | 1 | function isRedirect(code) { |
| 963 | 0 | return ~[301, 302, 303, 305, 307].indexOf(code); |
| 964 | } | |
| 965 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | 1 | exports['application/x-www-form-urlencoded'] = require('./urlencoded'); |
| 3 | 1 | exports['application/json'] = require('./json'); |
| 4 | 1 | exports.text = require('./text'); |
| 5 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | 1 | module.exports = function(res, fn){ |
| 3 | 0 | res.text = ''; |
| 4 | 0 | res.setEncoding('utf8'); |
| 5 | 0 | res.on('data', function(chunk){ res.text += chunk; }); |
| 6 | 0 | res.on('end', function(){ |
| 7 | 0 | try { |
| 8 | 0 | fn(null, JSON.parse(res.text)); |
| 9 | } catch (err) { | |
| 10 | 0 | fn(err); |
| 11 | } | |
| 12 | }); | |
| 13 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | 1 | module.exports = function(res, fn){ |
| 3 | 0 | res.text = ''; |
| 4 | 0 | res.setEncoding('utf8'); |
| 5 | 0 | res.on('data', function(chunk){ res.text += chunk; }); |
| 6 | 0 | res.on('end', fn); |
| 7 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var qs = require('qs'); |
| 7 | ||
| 8 | 1 | module.exports = function(res, fn){ |
| 9 | 0 | res.text = ''; |
| 10 | 0 | res.setEncoding('ascii'); |
| 11 | 0 | res.on('data', function(chunk){ res.text += chunk; }); |
| 12 | 0 | res.on('end', function(){ |
| 13 | 0 | try { |
| 14 | 0 | fn(null, qs.parse(res.text)); |
| 15 | } catch (err) { | |
| 16 | 0 | fn(err); |
| 17 | } | |
| 18 | }); | |
| 19 | }; |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('./utils'); |
| 7 | 1 | var Stream = require('stream').Stream; |
| 8 | 1 | var mime = require('mime'); |
| 9 | 1 | var path = require('path'); |
| 10 | 1 | var basename = path.basename; |
| 11 | ||
| 12 | /** | |
| 13 | * Expose `Part`. | |
| 14 | */ | |
| 15 | ||
| 16 | 1 | module.exports = Part; |
| 17 | ||
| 18 | /** | |
| 19 | * Initialize a new `Part` for the given `req`. | |
| 20 | * | |
| 21 | * @param {Request} req | |
| 22 | * @api public | |
| 23 | */ | |
| 24 | ||
| 25 | 1 | function Part(req) { |
| 26 | 0 | this.req = req; |
| 27 | 0 | this.header = {}; |
| 28 | 0 | this.headerSent = false; |
| 29 | 0 | this.request = req.request(); |
| 30 | 0 | this.writable = true; |
| 31 | 0 | if (!req._boundary) this.assignBoundary(); |
| 32 | } | |
| 33 | ||
| 34 | /** | |
| 35 | * Inherit from `Stream.prototype`. | |
| 36 | */ | |
| 37 | ||
| 38 | 1 | Part.prototype.__proto__ = Stream.prototype; |
| 39 | ||
| 40 | /** | |
| 41 | * Assign the initial request-level boundary. | |
| 42 | * | |
| 43 | * @api private | |
| 44 | */ | |
| 45 | ||
| 46 | 1 | Part.prototype.assignBoundary = function(){ |
| 47 | 0 | var boundary = utils.uid(32); |
| 48 | 0 | this.req.set('Content-Type', 'multipart/form-data; boundary=' + boundary); |
| 49 | 0 | this.req._boundary = boundary; |
| 50 | }; | |
| 51 | ||
| 52 | /** | |
| 53 | * Set header `field` to `val`. | |
| 54 | * | |
| 55 | * @param {String} field | |
| 56 | * @param {String} val | |
| 57 | * @return {Part} for chaining | |
| 58 | * @api public | |
| 59 | */ | |
| 60 | ||
| 61 | 1 | Part.prototype.set = function(field, val){ |
| 62 | 0 | if (!this._boundary) { |
| 63 | // TODO: formidable bug | |
| 64 | 0 | if (!this.request._hasFirstBoundary) { |
| 65 | 0 | this.request.write('--' + this.req._boundary + '\r\n'); |
| 66 | 0 | this.request._hasFirstBoundary = true; |
| 67 | } else { | |
| 68 | 0 | this.request.write('\r\n--' + this.req._boundary + '\r\n'); |
| 69 | } | |
| 70 | 0 | this._boundary = true; |
| 71 | } | |
| 72 | 0 | this.request.write(field + ': ' + val + '\r\n'); |
| 73 | 0 | return this; |
| 74 | }; | |
| 75 | ||
| 76 | /** | |
| 77 | * Set _Content-Type_ response header passed through `mime.lookup()`. | |
| 78 | * | |
| 79 | * Examples: | |
| 80 | * | |
| 81 | * res.type('html'); | |
| 82 | * res.type('.html'); | |
| 83 | * | |
| 84 | * @param {String} type | |
| 85 | * @return {Part} | |
| 86 | * @api public | |
| 87 | */ | |
| 88 | ||
| 89 | 1 | Part.prototype.type = function(type){ |
| 90 | 0 | return this.set('Content-Type', mime.lookup(type)); |
| 91 | }; | |
| 92 | ||
| 93 | /** | |
| 94 | * Set _Content-Disposition_ header field to _form-data_ | |
| 95 | * and set the _name_ param to the given string. | |
| 96 | * | |
| 97 | * @param {String} name | |
| 98 | * @return {Part} | |
| 99 | * @api public | |
| 100 | */ | |
| 101 | ||
| 102 | 1 | Part.prototype.name = function(name){ |
| 103 | 0 | this.set('Content-Disposition', 'form-data; name="' + name + '"'); |
| 104 | 0 | return this; |
| 105 | }; | |
| 106 | ||
| 107 | /** | |
| 108 | * Set _Content-Disposition_ header field to _attachment_ with `filename` | |
| 109 | * and field `name`. | |
| 110 | * | |
| 111 | * @param {String} name | |
| 112 | * @param {String} filename | |
| 113 | * @return {Part} | |
| 114 | * @api public | |
| 115 | */ | |
| 116 | ||
| 117 | 1 | Part.prototype.attachment = function(name, filename){ |
| 118 | 0 | this.type(filename); |
| 119 | 0 | this.set('Content-Disposition', 'attachment; name="' + name + '"; filename="' + basename(filename) + '"'); |
| 120 | 0 | return this; |
| 121 | }; | |
| 122 | ||
| 123 | /** | |
| 124 | * Write `data` with `encoding`. | |
| 125 | * | |
| 126 | * @param {Buffer|String} data | |
| 127 | * @param {String} encoding | |
| 128 | * @return {Boolean} | |
| 129 | * @api public | |
| 130 | */ | |
| 131 | ||
| 132 | 1 | Part.prototype.write = function(data, encoding){ |
| 133 | 0 | if (!this._headerCRLF) { |
| 134 | 0 | this.request.write('\r\n'); |
| 135 | 0 | this._headerCRLF = true; |
| 136 | } | |
| 137 | 0 | return this.request.write(data, encoding); |
| 138 | }; | |
| 139 | ||
| 140 | /** | |
| 141 | * End the part. | |
| 142 | * | |
| 143 | * @api public | |
| 144 | */ | |
| 145 | ||
| 146 | 1 | Part.prototype.end = function(){ |
| 147 | 0 | this.emit('end'); |
| 148 | 0 | this.complete = true; |
| 149 | }; | |
| 150 | ||
| 151 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var utils = require('./utils'); |
| 7 | 1 | var Stream = require('stream'); |
| 8 | ||
| 9 | /** | |
| 10 | * Expose `Response`. | |
| 11 | */ | |
| 12 | ||
| 13 | 1 | module.exports = Response; |
| 14 | ||
| 15 | /** | |
| 16 | * Initialize a new `Response` with the given `xhr`. | |
| 17 | * | |
| 18 | * - set flags (.ok, .error, etc) | |
| 19 | * - parse header | |
| 20 | * | |
| 21 | * @param {ClientRequest} req | |
| 22 | * @param {IncomingMessage} res | |
| 23 | * @param {Object} options | |
| 24 | * @constructor | |
| 25 | * @extends {Stream} | |
| 26 | * @implements {ReadableStream} | |
| 27 | * @api private | |
| 28 | */ | |
| 29 | ||
| 30 | 1 | function Response(req, res, options) { |
| 31 | 0 | options = options || {}; |
| 32 | 0 | this.req = req; |
| 33 | 0 | this.res = res; |
| 34 | 0 | this.links = {}; |
| 35 | 0 | this.text = res.text; |
| 36 | 0 | this.body = res.body || {}; |
| 37 | 0 | this.files = res.files || {}; |
| 38 | 0 | this.buffered = 'string' == typeof this.text; |
| 39 | 0 | this.header = this.headers = res.headers; |
| 40 | 0 | this.setStatusProperties(res.statusCode); |
| 41 | 0 | this.setHeaderProperties(this.header); |
| 42 | 0 | this.setEncoding = res.setEncoding.bind(res); |
| 43 | 0 | res.on('data', this.emit.bind(this, 'data')); |
| 44 | 0 | res.on('end', this.emit.bind(this, 'end')); |
| 45 | 0 | res.on('close', this.emit.bind(this, 'close')); |
| 46 | 0 | res.on('error', this.emit.bind(this, 'error')); |
| 47 | } | |
| 48 | ||
| 49 | /** | |
| 50 | * Inherits from `Stream.prototype`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | Response.prototype.__proto__ = Stream.prototype; |
| 54 | ||
| 55 | /** | |
| 56 | * Get case-insensitive `field` value. | |
| 57 | * | |
| 58 | * @param {String} field | |
| 59 | * @return {String} | |
| 60 | * @api public | |
| 61 | */ | |
| 62 | ||
| 63 | 1 | Response.prototype.get = function(field){ |
| 64 | 0 | return this.header[field.toLowerCase()]; |
| 65 | }; | |
| 66 | ||
| 67 | /** | |
| 68 | * Implements methods of a `ReadableStream` | |
| 69 | */ | |
| 70 | ||
| 71 | 1 | Response.prototype.destroy = function(err){ |
| 72 | 0 | this.res.destroy(err); |
| 73 | }; | |
| 74 | ||
| 75 | /** | |
| 76 | * Pause. | |
| 77 | */ | |
| 78 | ||
| 79 | 1 | Response.prototype.pause = function(){ |
| 80 | 0 | this.res.pause(); |
| 81 | }; | |
| 82 | ||
| 83 | /** | |
| 84 | * Resume. | |
| 85 | */ | |
| 86 | ||
| 87 | 1 | Response.prototype.resume = function(){ |
| 88 | 0 | this.res.resume(); |
| 89 | }; | |
| 90 | ||
| 91 | /** | |
| 92 | * Return an `Error` representative of this response. | |
| 93 | * | |
| 94 | * @return {Error} | |
| 95 | * @api public | |
| 96 | */ | |
| 97 | ||
| 98 | 1 | Response.prototype.toError = function(){ |
| 99 | 0 | var req = this.req; |
| 100 | 0 | var method = req.method; |
| 101 | 0 | var path = req.path; |
| 102 | ||
| 103 | 0 | var msg = 'cannot ' + method + ' ' + path + ' (' + this.status + ')'; |
| 104 | 0 | var err = new Error(msg); |
| 105 | 0 | err.status = this.status; |
| 106 | 0 | err.method = method; |
| 107 | 0 | err.path = path; |
| 108 | ||
| 109 | 0 | return err; |
| 110 | }; | |
| 111 | ||
| 112 | /** | |
| 113 | * Set header related properties: | |
| 114 | * | |
| 115 | * - `.type` the content type without params | |
| 116 | * | |
| 117 | * A response of "Content-Type: text/plain; charset=utf-8" | |
| 118 | * will provide you with a `.type` of "text/plain". | |
| 119 | * | |
| 120 | * @param {Object} header | |
| 121 | * @api private | |
| 122 | */ | |
| 123 | ||
| 124 | 1 | Response.prototype.setHeaderProperties = function(header){ |
| 125 | // TODO: moar! | |
| 126 | // TODO: make this a util | |
| 127 | ||
| 128 | // content-type | |
| 129 | 0 | var ct = this.header['content-type'] || ''; |
| 130 | ||
| 131 | // params | |
| 132 | 0 | var params = utils.params(ct); |
| 133 | 0 | for (var key in params) this[key] = params[key]; |
| 134 | ||
| 135 | 0 | this.type = utils.type(ct); |
| 136 | ||
| 137 | // links | |
| 138 | 0 | try { |
| 139 | 0 | if (header.link) this.links = utils.parseLinks(header.link); |
| 140 | } catch (err) { | |
| 141 | // ignore | |
| 142 | } | |
| 143 | }; | |
| 144 | ||
| 145 | /** | |
| 146 | * Parse cookies from the header into an array. | |
| 147 | */ | |
| 148 | ||
| 149 | 1 | function parseCookies(header) { |
| 150 | 0 | return Array.isArray(header) |
| 151 | ? header.map(Cookie.parse) | |
| 152 | : [Cookie.parse(header)]; | |
| 153 | } | |
| 154 | ||
| 155 | /** | |
| 156 | * Set flags such as `.ok` based on `status`. | |
| 157 | * | |
| 158 | * For example a 2xx response will give you a `.ok` of __true__ | |
| 159 | * whereas 5xx will be __false__ and `.error` will be __true__. The | |
| 160 | * `.clientError` and `.serverError` are also available to be more | |
| 161 | * specific, and `.statusType` is the class of error ranging from 1..5 | |
| 162 | * sometimes useful for mapping respond colors etc. | |
| 163 | * | |
| 164 | * "sugar" properties are also defined for common cases. Currently providing: | |
| 165 | * | |
| 166 | * - .noContent | |
| 167 | * - .badRequest | |
| 168 | * - .unauthorized | |
| 169 | * - .notAcceptable | |
| 170 | * - .notFound | |
| 171 | * | |
| 172 | * @param {Number} status | |
| 173 | * @api private | |
| 174 | */ | |
| 175 | ||
| 176 | 1 | Response.prototype.setStatusProperties = function(status){ |
| 177 | 0 | var type = status / 100 | 0; |
| 178 | ||
| 179 | // status / class | |
| 180 | 0 | this.status = this.statusCode = status; |
| 181 | 0 | this.statusType = type; |
| 182 | ||
| 183 | // basics | |
| 184 | 0 | this.info = 1 == type; |
| 185 | 0 | this.ok = 2 == type; |
| 186 | 0 | this.redirect = 3 == type; |
| 187 | 0 | this.clientError = 4 == type; |
| 188 | 0 | this.serverError = 5 == type; |
| 189 | 0 | this.error = (4 == type || 5 == type) |
| 190 | ? this.toError() | |
| 191 | : false; | |
| 192 | ||
| 193 | // sugar | |
| 194 | 0 | this.accepted = 202 == status; |
| 195 | 0 | this.noContent = 204 == status; |
| 196 | 0 | this.badRequest = 400 == status; |
| 197 | 0 | this.unauthorized = 401 == status; |
| 198 | 0 | this.notAcceptable = 406 == status; |
| 199 | 0 | this.forbidden = 403 == status; |
| 200 | 0 | this.notFound = 404 == status; |
| 201 | }; | |
| 202 |
| Line | Hits | Source |
|---|---|---|
| 1 | ||
| 2 | /** | |
| 3 | * Module dependencies. | |
| 4 | */ | |
| 5 | ||
| 6 | 1 | var StringDecoder = require('string_decoder').StringDecoder; |
| 7 | 1 | var Stream = require('stream'); |
| 8 | 1 | var zlib; |
| 9 | ||
| 10 | /** | |
| 11 | * Require zlib module for Node 0.6+ | |
| 12 | */ | |
| 13 | ||
| 14 | 1 | try { |
| 15 | 1 | zlib = require('zlib'); |
| 16 | } catch (e) { } | |
| 17 | ||
| 18 | /** | |
| 19 | * Generate a UID with the given `len`. | |
| 20 | * | |
| 21 | * @param {Number} len | |
| 22 | * @return {String} | |
| 23 | * @api private | |
| 24 | */ | |
| 25 | ||
| 26 | 1 | exports.uid = function(len){ |
| 27 | 0 | var buf = ''; |
| 28 | 0 | var chars = 'abcdefghijklmnopqrstuvwxyz123456789'; |
| 29 | 0 | var nchars = chars.length; |
| 30 | 0 | while (len--) buf += chars[Math.random() * nchars | 0]; |
| 31 | 0 | return buf; |
| 32 | }; | |
| 33 | ||
| 34 | /** | |
| 35 | * Return the mime type for the given `str`. | |
| 36 | * | |
| 37 | * @param {String} str | |
| 38 | * @return {String} | |
| 39 | * @api private | |
| 40 | */ | |
| 41 | ||
| 42 | 1 | exports.type = function(str){ |
| 43 | 0 | return str.split(/ *; */).shift(); |
| 44 | }; | |
| 45 | ||
| 46 | /** | |
| 47 | * Return header field parameters. | |
| 48 | * | |
| 49 | * @param {String} str | |
| 50 | * @return {Object} | |
| 51 | * @api private | |
| 52 | */ | |
| 53 | ||
| 54 | 1 | exports.params = function(str){ |
| 55 | 0 | return str.split(/ *; */).reduce(function(obj, str){ |
| 56 | 0 | var parts = str.split(/ *= */); |
| 57 | 0 | var key = parts.shift(); |
| 58 | 0 | var val = parts.shift(); |
| 59 | ||
| 60 | 0 | if (key && val) obj[key] = val; |
| 61 | 0 | return obj; |
| 62 | }, {}); | |
| 63 | }; | |
| 64 | ||
| 65 | /** | |
| 66 | * Parse Link header fields. | |
| 67 | * | |
| 68 | * @param {String} str | |
| 69 | * @return {Object} | |
| 70 | * @api private | |
| 71 | */ | |
| 72 | ||
| 73 | 1 | exports.parseLinks = function(str){ |
| 74 | 0 | return str.split(/ *, */).reduce(function(obj, str){ |
| 75 | 0 | var parts = str.split(/ *; */); |
| 76 | 0 | var url = parts[0].slice(1, -1); |
| 77 | 0 | var rel = parts[1].split(/ *= */)[1].slice(1, -1); |
| 78 | 0 | obj[rel] = url; |
| 79 | 0 | return obj; |
| 80 | }, {}); | |
| 81 | }; | |
| 82 | ||
| 83 | /** | |
| 84 | * Buffers response data events and re-emits when they're unzipped. | |
| 85 | * | |
| 86 | * @param {Request} req | |
| 87 | * @param {Response} res | |
| 88 | * @api private | |
| 89 | */ | |
| 90 | ||
| 91 | 1 | exports.unzip = function(req, res){ |
| 92 | 0 | if (!zlib) return; |
| 93 | ||
| 94 | 0 | var unzip = zlib.createUnzip(); |
| 95 | 0 | var stream = new Stream; |
| 96 | 0 | var decoder; |
| 97 | ||
| 98 | // make node responseOnEnd() happy | |
| 99 | 0 | stream.req = req; |
| 100 | ||
| 101 | 0 | unzip.on('error', function(err){ |
| 102 | 0 | stream.emit('error', err); |
| 103 | }); | |
| 104 | ||
| 105 | // pipe to unzip | |
| 106 | 0 | res.pipe(unzip); |
| 107 | ||
| 108 | // override `setEncoding` to capture encoding | |
| 109 | 0 | res.setEncoding = function(type){ |
| 110 | 0 | decoder = new StringDecoder(type); |
| 111 | }; | |
| 112 | ||
| 113 | // decode upon decompressing with captured encoding | |
| 114 | 0 | unzip.on('data', function(buf){ |
| 115 | 0 | if (decoder) { |
| 116 | 0 | var str = decoder.write(buf); |
| 117 | 0 | if (str.length) stream.emit('data', str); |
| 118 | } else { | |
| 119 | 0 | stream.emit('data', buf); |
| 120 | } | |
| 121 | }); | |
| 122 | ||
| 123 | 0 | unzip.on('end', function(){ |
| 124 | 0 | stream.emit('end'); |
| 125 | }); | |
| 126 | ||
| 127 | // override `on` to capture data listeners | |
| 128 | 0 | var _on = res.on; |
| 129 | 0 | res.on = function(type, fn){ |
| 130 | 0 | if ('data' == type || 'end' == type) { |
| 131 | 0 | stream.on(type, fn); |
| 132 | 0 | } else if ('error' == type) { |
| 133 | 0 | stream.on(type, fn); |
| 134 | 0 | _on.call(res, type, fn); |
| 135 | } else { | |
| 136 | 0 | _on.call(res, type, fn); |
| 137 | } | |
| 138 | }; | |
| 139 | }; | |
| 140 | ||
| 141 | /** | |
| 142 | * Strip content related fields from `header`. | |
| 143 | * | |
| 144 | * @param {Object} header | |
| 145 | * @return {Object} header | |
| 146 | * @api private | |
| 147 | */ | |
| 148 | ||
| 149 | 1 | exports.cleanHeader = function(header){ |
| 150 | 0 | delete header['content-type']; |
| 151 | 0 | delete header['content-length']; |
| 152 | 0 | delete header['transfer-encoding']; |
| 153 | 0 | delete header['cookie']; |
| 154 | 0 | delete header['host']; |
| 155 | 0 | return header; |
| 156 | }; | |
| 157 |
| Line | Hits | Source |
|---|---|---|
| 1 | /** | |
| 2 | * Module dependencies. | |
| 3 | */ | |
| 4 | ||
| 5 | 1 | var tty = require('tty'); |
| 6 | ||
| 7 | /** | |
| 8 | * Expose `debug()` as the module. | |
| 9 | */ | |
| 10 | ||
| 11 | 1 | module.exports = debug; |
| 12 | ||
| 13 | /** | |
| 14 | * Enabled debuggers. | |
| 15 | */ | |
| 16 | ||
| 17 | 1 | var names = [] |
| 18 | , skips = []; | |
| 19 | ||
| 20 | 1 | (process.env.DEBUG || '') |
| 21 | .split(/[\s,]+/) | |
| 22 | .forEach(function(name){ | |
| 23 | 1 | name = name.replace('*', '.*?'); |
| 24 | 1 | if (name[0] === '-') { |
| 25 | 0 | skips.push(new RegExp('^' + name.substr(1) + '$')); |
| 26 | } else { | |
| 27 | 1 | names.push(new RegExp('^' + name + '$')); |
| 28 | } | |
| 29 | }); | |
| 30 | ||
| 31 | /** | |
| 32 | * Colors. | |
| 33 | */ | |
| 34 | ||
| 35 | 1 | var colors = [6, 2, 3, 4, 5, 1]; |
| 36 | ||
| 37 | /** | |
| 38 | * Previous debug() call. | |
| 39 | */ | |
| 40 | ||
| 41 | 1 | var prev = {}; |
| 42 | ||
| 43 | /** | |
| 44 | * Previously assigned color. | |
| 45 | */ | |
| 46 | ||
| 47 | 1 | var prevColor = 0; |
| 48 | ||
| 49 | /** | |
| 50 | * Is stdout a TTY? Colored output is disabled when `true`. | |
| 51 | */ | |
| 52 | ||
| 53 | 1 | var isatty = tty.isatty(2); |
| 54 | ||
| 55 | /** | |
| 56 | * Select a color. | |
| 57 | * | |
| 58 | * @return {Number} | |
| 59 | * @api private | |
| 60 | */ | |
| 61 | ||
| 62 | 1 | function color() { |
| 63 | 0 | return colors[prevColor++ % colors.length]; |
| 64 | } | |
| 65 | ||
| 66 | /** | |
| 67 | * Humanize the given `ms`. | |
| 68 | * | |
| 69 | * @param {Number} m | |
| 70 | * @return {String} | |
| 71 | * @api private | |
| 72 | */ | |
| 73 | ||
| 74 | 1 | function humanize(ms) { |
| 75 | 0 | var sec = 1000 |
| 76 | , min = 60 * 1000 | |
| 77 | , hour = 60 * min; | |
| 78 | ||
| 79 | 0 | if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; |
| 80 | 0 | if (ms >= min) return (ms / min).toFixed(1) + 'm'; |
| 81 | 0 | if (ms >= sec) return (ms / sec | 0) + 's'; |
| 82 | 0 | return ms + 'ms'; |
| 83 | } | |
| 84 | ||
| 85 | /** | |
| 86 | * Create a debugger with the given `name`. | |
| 87 | * | |
| 88 | * @param {String} name | |
| 89 | * @return {Type} | |
| 90 | * @api public | |
| 91 | */ | |
| 92 | ||
| 93 | 1 | function debug(name) { |
| 94 | 1 | function disabled(){} |
| 95 | 1 | disabled.enabled = false; |
| 96 | ||
| 97 | 1 | var match = skips.some(function(re){ |
| 98 | 0 | return re.test(name); |
| 99 | }); | |
| 100 | ||
| 101 | 1 | if (match) return disabled; |
| 102 | ||
| 103 | 1 | match = names.some(function(re){ |
| 104 | 1 | return re.test(name); |
| 105 | }); | |
| 106 | ||
| 107 | 2 | if (!match) return disabled; |
| 108 | 0 | var c = color(); |
| 109 | ||
| 110 | 0 | function colored(fmt) { |
| 111 | 0 | fmt = coerce(fmt); |
| 112 | ||
| 113 | 0 | var curr = new Date; |
| 114 | 0 | var ms = curr - (prev[name] || curr); |
| 115 | 0 | prev[name] = curr; |
| 116 | ||
| 117 | 0 | fmt = ' \u001b[9' + c + 'm' + name + ' ' |
| 118 | + '\u001b[3' + c + 'm\u001b[90m' | |
| 119 | + fmt + '\u001b[3' + c + 'm' | |
| 120 | + ' +' + humanize(ms) + '\u001b[0m'; | |
| 121 | ||
| 122 | 0 | console.error.apply(this, arguments); |
| 123 | } | |
| 124 | ||
| 125 | 0 | function plain(fmt) { |
| 126 | 0 | fmt = coerce(fmt); |
| 127 | ||
| 128 | 0 | fmt = new Date().toUTCString() |
| 129 | + ' ' + name + ' ' + fmt; | |
| 130 | 0 | console.error.apply(this, arguments); |
| 131 | } | |
| 132 | ||
| 133 | 0 | colored.enabled = plain.enabled = true; |
| 134 | ||
| 135 | 0 | return isatty || process.env.DEBUG_COLORS |
| 136 | ? colored | |
| 137 | : plain; | |
| 138 | } | |
| 139 | ||
| 140 | /** | |
| 141 | * Coerce `val`. | |
| 142 | */ | |
| 143 | ||
| 144 | 1 | function coerce(val) { |
| 145 | 0 | if (val instanceof Error) return val.stack || val.message; |
| 146 | 0 | return val; |
| 147 | } | |
| 148 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var util = require('util'), |
| 4 | WriteStream = require('fs').WriteStream, | |
| 5 | EventEmitter = require('events').EventEmitter, | |
| 6 | crypto = require('crypto'); | |
| 7 | ||
| 8 | 1 | function File(properties) { |
| 9 | 0 | EventEmitter.call(this); |
| 10 | ||
| 11 | 0 | this.size = 0; |
| 12 | 0 | this.path = null; |
| 13 | 0 | this.name = null; |
| 14 | 0 | this.type = null; |
| 15 | 0 | this.hash = null; |
| 16 | 0 | this.lastModifiedDate = null; |
| 17 | ||
| 18 | 0 | this._writeStream = null; |
| 19 | ||
| 20 | 0 | for (var key in properties) { |
| 21 | 0 | this[key] = properties[key]; |
| 22 | } | |
| 23 | ||
| 24 | 0 | if(typeof this.hash === 'string') { |
| 25 | 0 | this.hash = crypto.createHash(properties.hash); |
| 26 | } else { | |
| 27 | 0 | this.hash = null; |
| 28 | } | |
| 29 | } | |
| 30 | 1 | module.exports = File; |
| 31 | 1 | util.inherits(File, EventEmitter); |
| 32 | ||
| 33 | 1 | File.prototype.open = function() { |
| 34 | 0 | this._writeStream = new WriteStream(this.path); |
| 35 | }; | |
| 36 | ||
| 37 | 1 | File.prototype.toJSON = function() { |
| 38 | 0 | return { |
| 39 | size: this.size, | |
| 40 | path: this.path, | |
| 41 | name: this.name, | |
| 42 | type: this.type, | |
| 43 | mtime: this.lastModifiedDate, | |
| 44 | length: this.length, | |
| 45 | filename: this.filename, | |
| 46 | mime: this.mime | |
| 47 | }; | |
| 48 | }; | |
| 49 | ||
| 50 | 1 | File.prototype.write = function(buffer, cb) { |
| 51 | 0 | var self = this; |
| 52 | 0 | if (self.hash) { |
| 53 | 0 | self.hash.update(buffer); |
| 54 | } | |
| 55 | 0 | this._writeStream.write(buffer, function() { |
| 56 | 0 | self.lastModifiedDate = new Date(); |
| 57 | 0 | self.size += buffer.length; |
| 58 | 0 | self.emit('progress', self.size); |
| 59 | 0 | cb(); |
| 60 | }); | |
| 61 | }; | |
| 62 | ||
| 63 | 1 | File.prototype.end = function(cb) { |
| 64 | 0 | var self = this; |
| 65 | 0 | if (self.hash) { |
| 66 | 0 | self.hash = self.hash.digest('hex'); |
| 67 | } | |
| 68 | 0 | this._writeStream.end(function() { |
| 69 | 0 | self.emit('end'); |
| 70 | 0 | cb(); |
| 71 | }); | |
| 72 | }; | |
| 73 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var fs = require('fs'); |
| 4 | 1 | var util = require('util'), |
| 5 | path = require('path'), | |
| 6 | File = require('./file'), | |
| 7 | MultipartParser = require('./multipart_parser').MultipartParser, | |
| 8 | QuerystringParser = require('./querystring_parser').QuerystringParser, | |
| 9 | OctetParser = require('./octet_parser').OctetParser, | |
| 10 | JSONParser = require('./json_parser').JSONParser, | |
| 11 | StringDecoder = require('string_decoder').StringDecoder, | |
| 12 | EventEmitter = require('events').EventEmitter, | |
| 13 | Stream = require('stream').Stream, | |
| 14 | os = require('os'); | |
| 15 | ||
| 16 | 1 | function IncomingForm(opts) { |
| 17 | 0 | if (!(this instanceof IncomingForm)) return new IncomingForm(opts); |
| 18 | 0 | EventEmitter.call(this); |
| 19 | ||
| 20 | 0 | opts=opts||{}; |
| 21 | ||
| 22 | 0 | this.error = null; |
| 23 | 0 | this.ended = false; |
| 24 | ||
| 25 | 0 | this.maxFields = opts.maxFields || 1000; |
| 26 | 0 | this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024; |
| 27 | 0 | this.keepExtensions = opts.keepExtensions || false; |
| 28 | 0 | this.uploadDir = opts.uploadDir || os.tmpDir(); |
| 29 | 0 | this.encoding = opts.encoding || 'utf-8'; |
| 30 | 0 | this.headers = null; |
| 31 | 0 | this.type = null; |
| 32 | 0 | this.hash = false; |
| 33 | ||
| 34 | 0 | this.bytesReceived = null; |
| 35 | 0 | this.bytesExpected = null; |
| 36 | ||
| 37 | 0 | this._parser = null; |
| 38 | 0 | this._flushing = 0; |
| 39 | 0 | this._fieldsSize = 0; |
| 40 | 0 | this.openedFiles = []; |
| 41 | ||
| 42 | 0 | return this; |
| 43 | }; | |
| 44 | 1 | util.inherits(IncomingForm, EventEmitter); |
| 45 | 1 | exports.IncomingForm = IncomingForm; |
| 46 | ||
| 47 | 1 | IncomingForm.prototype.parse = function(req, cb) { |
| 48 | 0 | this.pause = function() { |
| 49 | 0 | try { |
| 50 | 0 | req.pause(); |
| 51 | } catch (err) { | |
| 52 | // the stream was destroyed | |
| 53 | 0 | if (!this.ended) { |
| 54 | // before it was completed, crash & burn | |
| 55 | 0 | this._error(err); |
| 56 | } | |
| 57 | 0 | return false; |
| 58 | } | |
| 59 | 0 | return true; |
| 60 | }; | |
| 61 | ||
| 62 | 0 | this.resume = function() { |
| 63 | 0 | try { |
| 64 | 0 | req.resume(); |
| 65 | } catch (err) { | |
| 66 | // the stream was destroyed | |
| 67 | 0 | if (!this.ended) { |
| 68 | // before it was completed, crash & burn | |
| 69 | 0 | this._error(err); |
| 70 | } | |
| 71 | 0 | return false; |
| 72 | } | |
| 73 | ||
| 74 | 0 | return true; |
| 75 | }; | |
| 76 | ||
| 77 | // Setup callback first, so we don't miss anything from data events emitted | |
| 78 | // immediately. | |
| 79 | 0 | if (cb) { |
| 80 | 0 | var fields = {}, files = {}; |
| 81 | 0 | this |
| 82 | .on('field', function(name, value) { | |
| 83 | 0 | fields[name] = value; |
| 84 | }) | |
| 85 | .on('file', function(name, file) { | |
| 86 | 0 | files[name] = file; |
| 87 | }) | |
| 88 | .on('error', function(err) { | |
| 89 | 0 | cb(err, fields, files); |
| 90 | }) | |
| 91 | .on('end', function() { | |
| 92 | 0 | cb(null, fields, files); |
| 93 | }); | |
| 94 | } | |
| 95 | ||
| 96 | // Parse headers and setup the parser, ready to start listening for data. | |
| 97 | 0 | this.writeHeaders(req.headers); |
| 98 | ||
| 99 | // Start listening for data. | |
| 100 | 0 | var self = this; |
| 101 | 0 | req |
| 102 | .on('error', function(err) { | |
| 103 | 0 | self._error(err); |
| 104 | }) | |
| 105 | .on('aborted', function() { | |
| 106 | 0 | self.emit('aborted'); |
| 107 | 0 | self._error(new Error('Request aborted')); |
| 108 | }) | |
| 109 | .on('data', function(buffer) { | |
| 110 | 0 | self.write(buffer); |
| 111 | }) | |
| 112 | .on('end', function() { | |
| 113 | 0 | if (self.error) { |
| 114 | 0 | return; |
| 115 | } | |
| 116 | ||
| 117 | 0 | var err = self._parser.end(); |
| 118 | 0 | if (err) { |
| 119 | 0 | self._error(err); |
| 120 | } | |
| 121 | }); | |
| 122 | ||
| 123 | 0 | return this; |
| 124 | }; | |
| 125 | ||
| 126 | 1 | IncomingForm.prototype.writeHeaders = function(headers) { |
| 127 | 0 | this.headers = headers; |
| 128 | 0 | this._parseContentLength(); |
| 129 | 0 | this._parseContentType(); |
| 130 | }; | |
| 131 | ||
| 132 | 1 | IncomingForm.prototype.write = function(buffer) { |
| 133 | 0 | if (!this._parser) { |
| 134 | 0 | this._error(new Error('unintialized parser')); |
| 135 | 0 | return; |
| 136 | } | |
| 137 | ||
| 138 | 0 | this.bytesReceived += buffer.length; |
| 139 | 0 | this.emit('progress', this.bytesReceived, this.bytesExpected); |
| 140 | ||
| 141 | 0 | var bytesParsed = this._parser.write(buffer); |
| 142 | 0 | if (bytesParsed !== buffer.length) { |
| 143 | 0 | this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed')); |
| 144 | } | |
| 145 | ||
| 146 | 0 | return bytesParsed; |
| 147 | }; | |
| 148 | ||
| 149 | 1 | IncomingForm.prototype.pause = function() { |
| 150 | // this does nothing, unless overwritten in IncomingForm.parse | |
| 151 | 0 | return false; |
| 152 | }; | |
| 153 | ||
| 154 | 1 | IncomingForm.prototype.resume = function() { |
| 155 | // this does nothing, unless overwritten in IncomingForm.parse | |
| 156 | 0 | return false; |
| 157 | }; | |
| 158 | ||
| 159 | 1 | IncomingForm.prototype.onPart = function(part) { |
| 160 | // this method can be overwritten by the user | |
| 161 | 0 | this.handlePart(part); |
| 162 | }; | |
| 163 | ||
| 164 | 1 | IncomingForm.prototype.handlePart = function(part) { |
| 165 | 0 | var self = this; |
| 166 | ||
| 167 | 0 | if (part.filename === undefined) { |
| 168 | 0 | var value = '' |
| 169 | , decoder = new StringDecoder(this.encoding); | |
| 170 | ||
| 171 | 0 | part.on('data', function(buffer) { |
| 172 | 0 | self._fieldsSize += buffer.length; |
| 173 | 0 | if (self._fieldsSize > self.maxFieldsSize) { |
| 174 | 0 | self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data')); |
| 175 | 0 | return; |
| 176 | } | |
| 177 | 0 | value += decoder.write(buffer); |
| 178 | }); | |
| 179 | ||
| 180 | 0 | part.on('end', function() { |
| 181 | 0 | self.emit('field', part.name, value); |
| 182 | }); | |
| 183 | 0 | return; |
| 184 | } | |
| 185 | ||
| 186 | 0 | this._flushing++; |
| 187 | ||
| 188 | 0 | var file = new File({ |
| 189 | path: this._uploadPath(part.filename), | |
| 190 | name: part.filename, | |
| 191 | type: part.mime, | |
| 192 | hash: self.hash | |
| 193 | }); | |
| 194 | ||
| 195 | 0 | this.emit('fileBegin', part.name, file); |
| 196 | ||
| 197 | 0 | file.open(); |
| 198 | 0 | this.openedFiles.push(file); |
| 199 | ||
| 200 | 0 | part.on('data', function(buffer) { |
| 201 | 0 | self.pause(); |
| 202 | 0 | file.write(buffer, function() { |
| 203 | 0 | self.resume(); |
| 204 | }); | |
| 205 | }); | |
| 206 | ||
| 207 | 0 | part.on('end', function() { |
| 208 | 0 | file.end(function() { |
| 209 | 0 | self._flushing--; |
| 210 | 0 | self.emit('file', part.name, file); |
| 211 | 0 | self._maybeEnd(); |
| 212 | }); | |
| 213 | }); | |
| 214 | }; | |
| 215 | ||
| 216 | 1 | function dummyParser(self) { |
| 217 | 0 | return { |
| 218 | end: function () { | |
| 219 | 0 | self.ended = true; |
| 220 | 0 | self._maybeEnd(); |
| 221 | 0 | return null; |
| 222 | } | |
| 223 | }; | |
| 224 | } | |
| 225 | ||
| 226 | 1 | IncomingForm.prototype._parseContentType = function() { |
| 227 | 0 | if (this.bytesExpected === 0) { |
| 228 | 0 | this._parser = dummyParser(this); |
| 229 | 0 | return; |
| 230 | } | |
| 231 | ||
| 232 | 0 | if (!this.headers['content-type']) { |
| 233 | 0 | this._error(new Error('bad content-type header, no content-type')); |
| 234 | 0 | return; |
| 235 | } | |
| 236 | ||
| 237 | 0 | if (this.headers['content-type'].match(/octet-stream/i)) { |
| 238 | 0 | this._initOctetStream(); |
| 239 | 0 | return; |
| 240 | } | |
| 241 | ||
| 242 | 0 | if (this.headers['content-type'].match(/urlencoded/i)) { |
| 243 | 0 | this._initUrlencoded(); |
| 244 | 0 | return; |
| 245 | } | |
| 246 | ||
| 247 | 0 | if (this.headers['content-type'].match(/multipart/i)) { |
| 248 | 0 | var m; |
| 249 | 0 | if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) { |
| 250 | 0 | this._initMultipart(m[1] || m[2]); |
| 251 | } else { | |
| 252 | 0 | this._error(new Error('bad content-type header, no multipart boundary')); |
| 253 | } | |
| 254 | 0 | return; |
| 255 | } | |
| 256 | ||
| 257 | 0 | if (this.headers['content-type'].match(/json/i)) { |
| 258 | 0 | this._initJSONencoded(); |
| 259 | 0 | return; |
| 260 | } | |
| 261 | ||
| 262 | 0 | this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type'])); |
| 263 | }; | |
| 264 | ||
| 265 | 1 | IncomingForm.prototype._error = function(err) { |
| 266 | 0 | if (this.error || this.ended) { |
| 267 | 0 | return; |
| 268 | } | |
| 269 | ||
| 270 | 0 | this.error = err; |
| 271 | 0 | this.pause(); |
| 272 | 0 | this.emit('error', err); |
| 273 | ||
| 274 | 0 | if (Array.isArray(this.openedFiles)) { |
| 275 | 0 | this.openedFiles.forEach(function(file) { |
| 276 | 0 | file._writeStream.destroy(); |
| 277 | 0 | setTimeout(fs.unlink, 0, file.path); |
| 278 | }); | |
| 279 | } | |
| 280 | }; | |
| 281 | ||
| 282 | 1 | IncomingForm.prototype._parseContentLength = function() { |
| 283 | 0 | this.bytesReceived = 0; |
| 284 | 0 | if (this.headers['content-length']) { |
| 285 | 0 | this.bytesExpected = parseInt(this.headers['content-length'], 10); |
| 286 | 0 | } else if (this.headers['transfer-encoding'] === undefined) { |
| 287 | 0 | this.bytesExpected = 0; |
| 288 | } | |
| 289 | ||
| 290 | 0 | if (this.bytesExpected !== null) { |
| 291 | 0 | this.emit('progress', this.bytesReceived, this.bytesExpected); |
| 292 | } | |
| 293 | }; | |
| 294 | ||
| 295 | 1 | IncomingForm.prototype._newParser = function() { |
| 296 | 0 | return new MultipartParser(); |
| 297 | }; | |
| 298 | ||
| 299 | 1 | IncomingForm.prototype._initMultipart = function(boundary) { |
| 300 | 0 | this.type = 'multipart'; |
| 301 | ||
| 302 | 0 | var parser = new MultipartParser(), |
| 303 | self = this, | |
| 304 | headerField, | |
| 305 | headerValue, | |
| 306 | part; | |
| 307 | ||
| 308 | 0 | parser.initWithBoundary(boundary); |
| 309 | ||
| 310 | 0 | parser.onPartBegin = function() { |
| 311 | 0 | part = new Stream(); |
| 312 | 0 | part.readable = true; |
| 313 | 0 | part.headers = {}; |
| 314 | 0 | part.name = null; |
| 315 | 0 | part.filename = null; |
| 316 | 0 | part.mime = null; |
| 317 | ||
| 318 | 0 | part.transferEncoding = 'binary'; |
| 319 | 0 | part.transferBuffer = ''; |
| 320 | ||
| 321 | 0 | headerField = ''; |
| 322 | 0 | headerValue = ''; |
| 323 | }; | |
| 324 | ||
| 325 | 0 | parser.onHeaderField = function(b, start, end) { |
| 326 | 0 | headerField += b.toString(self.encoding, start, end); |
| 327 | }; | |
| 328 | ||
| 329 | 0 | parser.onHeaderValue = function(b, start, end) { |
| 330 | 0 | headerValue += b.toString(self.encoding, start, end); |
| 331 | }; | |
| 332 | ||
| 333 | 0 | parser.onHeaderEnd = function() { |
| 334 | 0 | headerField = headerField.toLowerCase(); |
| 335 | 0 | part.headers[headerField] = headerValue; |
| 336 | ||
| 337 | 0 | var m; |
| 338 | 0 | if (headerField == 'content-disposition') { |
| 339 | 0 | if (m = headerValue.match(/\bname="([^"]+)"/i)) { |
| 340 | 0 | part.name = m[1]; |
| 341 | } | |
| 342 | ||
| 343 | 0 | part.filename = self._fileName(headerValue); |
| 344 | 0 | } else if (headerField == 'content-type') { |
| 345 | 0 | part.mime = headerValue; |
| 346 | 0 | } else if (headerField == 'content-transfer-encoding') { |
| 347 | 0 | part.transferEncoding = headerValue.toLowerCase(); |
| 348 | } | |
| 349 | ||
| 350 | 0 | headerField = ''; |
| 351 | 0 | headerValue = ''; |
| 352 | }; | |
| 353 | ||
| 354 | 0 | parser.onHeadersEnd = function() { |
| 355 | 0 | switch(part.transferEncoding){ |
| 356 | case 'binary': | |
| 357 | case '7bit': | |
| 358 | case '8bit': | |
| 359 | 0 | parser.onPartData = function(b, start, end) { |
| 360 | 0 | part.emit('data', b.slice(start, end)); |
| 361 | }; | |
| 362 | ||
| 363 | 0 | parser.onPartEnd = function() { |
| 364 | 0 | part.emit('end'); |
| 365 | }; | |
| 366 | 0 | break; |
| 367 | ||
| 368 | case 'base64': | |
| 369 | 0 | parser.onPartData = function(b, start, end) { |
| 370 | 0 | part.transferBuffer += b.slice(start, end).toString('ascii'); |
| 371 | ||
| 372 | /* | |
| 373 | four bytes (chars) in base64 converts to three bytes in binary | |
| 374 | encoding. So we should always work with a number of bytes that | |
| 375 | can be divided by 4, it will result in a number of buytes that | |
| 376 | can be divided vy 3. | |
| 377 | */ | |
| 378 | 0 | var offset = parseInt(part.transferBuffer.length / 4) * 4; |
| 379 | 0 | part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64')) |
| 380 | 0 | part.transferBuffer = part.transferBuffer.substring(offset); |
| 381 | }; | |
| 382 | ||
| 383 | 0 | parser.onPartEnd = function() { |
| 384 | 0 | part.emit('data', new Buffer(part.transferBuffer, 'base64')) |
| 385 | 0 | part.emit('end'); |
| 386 | }; | |
| 387 | 0 | break; |
| 388 | ||
| 389 | default: | |
| 390 | 0 | return self._error(new Error('unknown transfer-encoding')); |
| 391 | } | |
| 392 | ||
| 393 | 0 | self.onPart(part); |
| 394 | }; | |
| 395 | ||
| 396 | ||
| 397 | 0 | parser.onEnd = function() { |
| 398 | 0 | self.ended = true; |
| 399 | 0 | self._maybeEnd(); |
| 400 | }; | |
| 401 | ||
| 402 | 0 | this._parser = parser; |
| 403 | }; | |
| 404 | ||
| 405 | 1 | IncomingForm.prototype._fileName = function(headerValue) { |
| 406 | 0 | var m = headerValue.match(/\bfilename="(.*?)"($|; )/i); |
| 407 | 0 | if (!m) return; |
| 408 | ||
| 409 | 0 | var filename = m[1].substr(m[1].lastIndexOf('\\') + 1); |
| 410 | 0 | filename = filename.replace(/%22/g, '"'); |
| 411 | 0 | filename = filename.replace(/&#([\d]{4});/g, function(m, code) { |
| 412 | 0 | return String.fromCharCode(code); |
| 413 | }); | |
| 414 | 0 | return filename; |
| 415 | }; | |
| 416 | ||
| 417 | 1 | IncomingForm.prototype._initUrlencoded = function() { |
| 418 | 0 | this.type = 'urlencoded'; |
| 419 | ||
| 420 | 0 | var parser = new QuerystringParser(this.maxFields) |
| 421 | , self = this; | |
| 422 | ||
| 423 | 0 | parser.onField = function(key, val) { |
| 424 | 0 | self.emit('field', key, val); |
| 425 | }; | |
| 426 | ||
| 427 | 0 | parser.onEnd = function() { |
| 428 | 0 | self.ended = true; |
| 429 | 0 | self._maybeEnd(); |
| 430 | }; | |
| 431 | ||
| 432 | 0 | this._parser = parser; |
| 433 | }; | |
| 434 | ||
| 435 | 1 | IncomingForm.prototype._initOctetStream = function() { |
| 436 | 0 | this.type = 'octet-stream'; |
| 437 | 0 | var filename = this.headers['x-file-name']; |
| 438 | 0 | var mime = this.headers['content-type']; |
| 439 | ||
| 440 | 0 | var file = new File({ |
| 441 | path: this._uploadPath(filename), | |
| 442 | name: filename, | |
| 443 | type: mime | |
| 444 | }); | |
| 445 | ||
| 446 | 0 | file.open(); |
| 447 | ||
| 448 | 0 | this.emit('fileBegin', filename, file); |
| 449 | ||
| 450 | 0 | this._flushing++; |
| 451 | ||
| 452 | 0 | var self = this; |
| 453 | ||
| 454 | 0 | self._parser = new OctetParser(); |
| 455 | ||
| 456 | //Keep track of writes that haven't finished so we don't emit the file before it's done being written | |
| 457 | 0 | var outstandingWrites = 0; |
| 458 | ||
| 459 | 0 | self._parser.on('data', function(buffer){ |
| 460 | 0 | self.pause(); |
| 461 | 0 | outstandingWrites++; |
| 462 | ||
| 463 | 0 | file.write(buffer, function() { |
| 464 | 0 | outstandingWrites--; |
| 465 | 0 | self.resume(); |
| 466 | ||
| 467 | 0 | if(self.ended){ |
| 468 | 0 | self._parser.emit('doneWritingFile'); |
| 469 | } | |
| 470 | }); | |
| 471 | }); | |
| 472 | ||
| 473 | 0 | self._parser.on('end', function(){ |
| 474 | 0 | self._flushing--; |
| 475 | 0 | self.ended = true; |
| 476 | ||
| 477 | 0 | var done = function(){ |
| 478 | 0 | self.emit('file', 'file', file); |
| 479 | 0 | self._maybeEnd(); |
| 480 | }; | |
| 481 | ||
| 482 | 0 | if(outstandingWrites === 0){ |
| 483 | 0 | done(); |
| 484 | } else { | |
| 485 | 0 | self._parser.once('doneWritingFile', done); |
| 486 | } | |
| 487 | }); | |
| 488 | }; | |
| 489 | ||
| 490 | 1 | IncomingForm.prototype._initJSONencoded = function() { |
| 491 | 0 | this.type = 'json'; |
| 492 | ||
| 493 | 0 | var parser = new JSONParser() |
| 494 | , self = this; | |
| 495 | ||
| 496 | 0 | if (this.bytesExpected) { |
| 497 | 0 | parser.initWithLength(this.bytesExpected); |
| 498 | } | |
| 499 | ||
| 500 | 0 | parser.onField = function(key, val) { |
| 501 | 0 | self.emit('field', key, val); |
| 502 | } | |
| 503 | ||
| 504 | 0 | parser.onEnd = function() { |
| 505 | 0 | self.ended = true; |
| 506 | 0 | self._maybeEnd(); |
| 507 | }; | |
| 508 | ||
| 509 | 0 | this._parser = parser; |
| 510 | }; | |
| 511 | ||
| 512 | 1 | IncomingForm.prototype._uploadPath = function(filename) { |
| 513 | 0 | var name = ''; |
| 514 | 0 | for (var i = 0; i < 32; i++) { |
| 515 | 0 | name += Math.floor(Math.random() * 16).toString(16); |
| 516 | } | |
| 517 | ||
| 518 | 0 | if (this.keepExtensions) { |
| 519 | 0 | var ext = path.extname(filename); |
| 520 | 0 | ext = ext.replace(/(\.[a-z0-9]+).*/, '$1'); |
| 521 | ||
| 522 | 0 | name += ext; |
| 523 | } | |
| 524 | ||
| 525 | 0 | return path.join(this.uploadDir, name); |
| 526 | }; | |
| 527 | ||
| 528 | 1 | IncomingForm.prototype._maybeEnd = function() { |
| 529 | 0 | if (!this.ended || this._flushing || this.error) { |
| 530 | 0 | return; |
| 531 | } | |
| 532 | ||
| 533 | 0 | this.emit('end'); |
| 534 | }; | |
| 535 | ||
| 536 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var IncomingForm = require('./incoming_form').IncomingForm; |
| 2 | 1 | IncomingForm.IncomingForm = IncomingForm; |
| 3 | 1 | module.exports = IncomingForm; |
| 4 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | 1 | var Buffer = require('buffer').Buffer |
| 4 | ||
| 5 | 1 | function JSONParser() { |
| 6 | 0 | this.data = new Buffer(''); |
| 7 | 0 | this.bytesWritten = 0; |
| 8 | }; | |
| 9 | 1 | exports.JSONParser = JSONParser; |
| 10 | ||
| 11 | 1 | JSONParser.prototype.initWithLength = function(length) { |
| 12 | 0 | this.data = new Buffer(length); |
| 13 | } | |
| 14 | ||
| 15 | 1 | JSONParser.prototype.write = function(buffer) { |
| 16 | 0 | if (this.data.length >= this.bytesWritten + buffer.length) { |
| 17 | 0 | buffer.copy(this.data, this.bytesWritten); |
| 18 | } else { | |
| 19 | 0 | this.data = Buffer.concat([this.data, buffer]); |
| 20 | } | |
| 21 | 0 | this.bytesWritten += buffer.length; |
| 22 | 0 | return buffer.length; |
| 23 | } | |
| 24 | ||
| 25 | 1 | JSONParser.prototype.end = function() { |
| 26 | 0 | try { |
| 27 | 0 | var fields = JSON.parse(this.data.toString('utf8')) |
| 28 | 0 | for (var field in fields) { |
| 29 | 0 | this.onField(field, fields[field]); |
| 30 | } | |
| 31 | } catch (e) {} | |
| 32 | 0 | this.data = null; |
| 33 | ||
| 34 | 0 | this.onEnd(); |
| 35 | } |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var Buffer = require('buffer').Buffer, |
| 2 | s = 0, | |
| 3 | S = | |
| 4 | { PARSER_UNINITIALIZED: s++, | |
| 5 | START: s++, | |
| 6 | START_BOUNDARY: s++, | |
| 7 | HEADER_FIELD_START: s++, | |
| 8 | HEADER_FIELD: s++, | |
| 9 | HEADER_VALUE_START: s++, | |
| 10 | HEADER_VALUE: s++, | |
| 11 | HEADER_VALUE_ALMOST_DONE: s++, | |
| 12 | HEADERS_ALMOST_DONE: s++, | |
| 13 | PART_DATA_START: s++, | |
| 14 | PART_DATA: s++, | |
| 15 | PART_END: s++, | |
| 16 | END: s++ | |
| 17 | }, | |
| 18 | ||
| 19 | f = 1, | |
| 20 | F = | |
| 21 | { PART_BOUNDARY: f, | |
| 22 | LAST_BOUNDARY: f *= 2 | |
| 23 | }, | |
| 24 | ||
| 25 | LF = 10, | |
| 26 | CR = 13, | |
| 27 | SPACE = 32, | |
| 28 | HYPHEN = 45, | |
| 29 | COLON = 58, | |
| 30 | A = 97, | |
| 31 | Z = 122, | |
| 32 | ||
| 33 | lower = function(c) { | |
| 34 | 0 | return c | 0x20; |
| 35 | }; | |
| 36 | ||
| 37 | 1 | for (s in S) { |
| 38 | 13 | exports[s] = S[s]; |
| 39 | } | |
| 40 | ||
| 41 | 1 | function MultipartParser() { |
| 42 | 0 | this.boundary = null; |
| 43 | 0 | this.boundaryChars = null; |
| 44 | 0 | this.lookbehind = null; |
| 45 | 0 | this.state = S.PARSER_UNINITIALIZED; |
| 46 | ||
| 47 | 0 | this.index = null; |
| 48 | 0 | this.flags = 0; |
| 49 | }; | |
| 50 | 1 | exports.MultipartParser = MultipartParser; |
| 51 | ||
| 52 | 1 | MultipartParser.stateToString = function(stateNumber) { |
| 53 | 0 | for (var state in S) { |
| 54 | 0 | var number = S[state]; |
| 55 | 0 | if (number === stateNumber) return state; |
| 56 | } | |
| 57 | }; | |
| 58 | ||
| 59 | 1 | MultipartParser.prototype.initWithBoundary = function(str) { |
| 60 | 0 | this.boundary = new Buffer(str.length+4); |
| 61 | 0 | this.boundary.write('\r\n--', 'ascii', 0); |
| 62 | 0 | this.boundary.write(str, 'ascii', 4); |
| 63 | 0 | this.lookbehind = new Buffer(this.boundary.length+8); |
| 64 | 0 | this.state = S.START; |
| 65 | ||
| 66 | 0 | this.boundaryChars = {}; |
| 67 | 0 | for (var i = 0; i < this.boundary.length; i++) { |
| 68 | 0 | this.boundaryChars[this.boundary[i]] = true; |
| 69 | } | |
| 70 | }; | |
| 71 | ||
| 72 | 1 | MultipartParser.prototype.write = function(buffer) { |
| 73 | 0 | var self = this, |
| 74 | i = 0, | |
| 75 | len = buffer.length, | |
| 76 | prevIndex = this.index, | |
| 77 | index = this.index, | |
| 78 | state = this.state, | |
| 79 | flags = this.flags, | |
| 80 | lookbehind = this.lookbehind, | |
| 81 | boundary = this.boundary, | |
| 82 | boundaryChars = this.boundaryChars, | |
| 83 | boundaryLength = this.boundary.length, | |
| 84 | boundaryEnd = boundaryLength - 1, | |
| 85 | bufferLength = buffer.length, | |
| 86 | c, | |
| 87 | cl, | |
| 88 | ||
| 89 | mark = function(name) { | |
| 90 | 0 | self[name+'Mark'] = i; |
| 91 | }, | |
| 92 | clear = function(name) { | |
| 93 | 0 | delete self[name+'Mark']; |
| 94 | }, | |
| 95 | callback = function(name, buffer, start, end) { | |
| 96 | 0 | if (start !== undefined && start === end) { |
| 97 | 0 | return; |
| 98 | } | |
| 99 | ||
| 100 | 0 | var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); |
| 101 | 0 | if (callbackSymbol in self) { |
| 102 | 0 | self[callbackSymbol](buffer, start, end); |
| 103 | } | |
| 104 | }, | |
| 105 | dataCallback = function(name, clear) { | |
| 106 | 0 | var markSymbol = name+'Mark'; |
| 107 | 0 | if (!(markSymbol in self)) { |
| 108 | 0 | return; |
| 109 | } | |
| 110 | ||
| 111 | 0 | if (!clear) { |
| 112 | 0 | callback(name, buffer, self[markSymbol], buffer.length); |
| 113 | 0 | self[markSymbol] = 0; |
| 114 | } else { | |
| 115 | 0 | callback(name, buffer, self[markSymbol], i); |
| 116 | 0 | delete self[markSymbol]; |
| 117 | } | |
| 118 | }; | |
| 119 | ||
| 120 | 0 | for (i = 0; i < len; i++) { |
| 121 | 0 | c = buffer[i]; |
| 122 | 0 | switch (state) { |
| 123 | case S.PARSER_UNINITIALIZED: | |
| 124 | 0 | return i; |
| 125 | case S.START: | |
| 126 | 0 | index = 0; |
| 127 | 0 | state = S.START_BOUNDARY; |
| 128 | case S.START_BOUNDARY: | |
| 129 | 0 | if (index == boundary.length - 2) { |
| 130 | 0 | if (c != CR) { |
| 131 | 0 | return i; |
| 132 | } | |
| 133 | 0 | index++; |
| 134 | 0 | break; |
| 135 | 0 | } else if (index - 1 == boundary.length - 2) { |
| 136 | 0 | if (c != LF) { |
| 137 | 0 | return i; |
| 138 | } | |
| 139 | 0 | index = 0; |
| 140 | 0 | callback('partBegin'); |
| 141 | 0 | state = S.HEADER_FIELD_START; |
| 142 | 0 | break; |
| 143 | } | |
| 144 | ||
| 145 | 0 | if (c != boundary[index+2]) { |
| 146 | 0 | index = -2; |
| 147 | } | |
| 148 | 0 | if (c == boundary[index+2]) { |
| 149 | 0 | index++; |
| 150 | } | |
| 151 | 0 | break; |
| 152 | case S.HEADER_FIELD_START: | |
| 153 | 0 | state = S.HEADER_FIELD; |
| 154 | 0 | mark('headerField'); |
| 155 | 0 | index = 0; |
| 156 | case S.HEADER_FIELD: | |
| 157 | 0 | if (c == CR) { |
| 158 | 0 | clear('headerField'); |
| 159 | 0 | state = S.HEADERS_ALMOST_DONE; |
| 160 | 0 | break; |
| 161 | } | |
| 162 | ||
| 163 | 0 | index++; |
| 164 | 0 | if (c == HYPHEN) { |
| 165 | 0 | break; |
| 166 | } | |
| 167 | ||
| 168 | 0 | if (c == COLON) { |
| 169 | 0 | if (index == 1) { |
| 170 | // empty header field | |
| 171 | 0 | return i; |
| 172 | } | |
| 173 | 0 | dataCallback('headerField', true); |
| 174 | 0 | state = S.HEADER_VALUE_START; |
| 175 | 0 | break; |
| 176 | } | |
| 177 | ||
| 178 | 0 | cl = lower(c); |
| 179 | 0 | if (cl < A || cl > Z) { |
| 180 | 0 | return i; |
| 181 | } | |
| 182 | 0 | break; |
| 183 | case S.HEADER_VALUE_START: | |
| 184 | 0 | if (c == SPACE) { |
| 185 | 0 | break; |
| 186 | } | |
| 187 | ||
| 188 | 0 | mark('headerValue'); |
| 189 | 0 | state = S.HEADER_VALUE; |
| 190 | case S.HEADER_VALUE: | |
| 191 | 0 | if (c == CR) { |
| 192 | 0 | dataCallback('headerValue', true); |
| 193 | 0 | callback('headerEnd'); |
| 194 | 0 | state = S.HEADER_VALUE_ALMOST_DONE; |
| 195 | } | |
| 196 | 0 | break; |
| 197 | case S.HEADER_VALUE_ALMOST_DONE: | |
| 198 | 0 | if (c != LF) { |
| 199 | 0 | return i; |
| 200 | } | |
| 201 | 0 | state = S.HEADER_FIELD_START; |
| 202 | 0 | break; |
| 203 | case S.HEADERS_ALMOST_DONE: | |
| 204 | 0 | if (c != LF) { |
| 205 | 0 | return i; |
| 206 | } | |
| 207 | ||
| 208 | 0 | callback('headersEnd'); |
| 209 | 0 | state = S.PART_DATA_START; |
| 210 | 0 | break; |
| 211 | case S.PART_DATA_START: | |
| 212 | 0 | state = S.PART_DATA; |
| 213 | 0 | mark('partData'); |
| 214 | case S.PART_DATA: | |
| 215 | 0 | prevIndex = index; |
| 216 | ||
| 217 | 0 | if (index == 0) { |
| 218 | // boyer-moore derrived algorithm to safely skip non-boundary data | |
| 219 | 0 | i += boundaryEnd; |
| 220 | 0 | while (i < bufferLength && !(buffer[i] in boundaryChars)) { |
| 221 | 0 | i += boundaryLength; |
| 222 | } | |
| 223 | 0 | i -= boundaryEnd; |
| 224 | 0 | c = buffer[i]; |
| 225 | } | |
| 226 | ||
| 227 | 0 | if (index < boundary.length) { |
| 228 | 0 | if (boundary[index] == c) { |
| 229 | 0 | if (index == 0) { |
| 230 | 0 | dataCallback('partData', true); |
| 231 | } | |
| 232 | 0 | index++; |
| 233 | } else { | |
| 234 | 0 | index = 0; |
| 235 | } | |
| 236 | 0 | } else if (index == boundary.length) { |
| 237 | 0 | index++; |
| 238 | 0 | if (c == CR) { |
| 239 | // CR = part boundary | |
| 240 | 0 | flags |= F.PART_BOUNDARY; |
| 241 | 0 | } else if (c == HYPHEN) { |
| 242 | // HYPHEN = end boundary | |
| 243 | 0 | flags |= F.LAST_BOUNDARY; |
| 244 | } else { | |
| 245 | 0 | index = 0; |
| 246 | } | |
| 247 | 0 | } else if (index - 1 == boundary.length) { |
| 248 | 0 | if (flags & F.PART_BOUNDARY) { |
| 249 | 0 | index = 0; |
| 250 | 0 | if (c == LF) { |
| 251 | // unset the PART_BOUNDARY flag | |
| 252 | 0 | flags &= ~F.PART_BOUNDARY; |
| 253 | 0 | callback('partEnd'); |
| 254 | 0 | callback('partBegin'); |
| 255 | 0 | state = S.HEADER_FIELD_START; |
| 256 | 0 | break; |
| 257 | } | |
| 258 | 0 | } else if (flags & F.LAST_BOUNDARY) { |
| 259 | 0 | if (c == HYPHEN) { |
| 260 | 0 | callback('partEnd'); |
| 261 | 0 | callback('end'); |
| 262 | 0 | state = S.END; |
| 263 | } else { | |
| 264 | 0 | index = 0; |
| 265 | } | |
| 266 | } else { | |
| 267 | 0 | index = 0; |
| 268 | } | |
| 269 | } | |
| 270 | ||
| 271 | 0 | if (index > 0) { |
| 272 | // when matching a possible boundary, keep a lookbehind reference | |
| 273 | // in case it turns out to be a false lead | |
| 274 | 0 | lookbehind[index-1] = c; |
| 275 | 0 | } else if (prevIndex > 0) { |
| 276 | // if our boundary turned out to be rubbish, the captured lookbehind | |
| 277 | // belongs to partData | |
| 278 | 0 | callback('partData', lookbehind, 0, prevIndex); |
| 279 | 0 | prevIndex = 0; |
| 280 | 0 | mark('partData'); |
| 281 | ||
| 282 | // reconsider the current character even so it interrupted the sequence | |
| 283 | // it could be the beginning of a new sequence | |
| 284 | 0 | i--; |
| 285 | } | |
| 286 | ||
| 287 | 0 | break; |
| 288 | case S.END: | |
| 289 | 0 | break; |
| 290 | default: | |
| 291 | 0 | return i; |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | 0 | dataCallback('headerField'); |
| 296 | 0 | dataCallback('headerValue'); |
| 297 | 0 | dataCallback('partData'); |
| 298 | ||
| 299 | 0 | this.index = index; |
| 300 | 0 | this.state = state; |
| 301 | 0 | this.flags = flags; |
| 302 | ||
| 303 | 0 | return len; |
| 304 | }; | |
| 305 | ||
| 306 | 1 | MultipartParser.prototype.end = function() { |
| 307 | 0 | var callback = function(self, name) { |
| 308 | 0 | var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); |
| 309 | 0 | if (callbackSymbol in self) { |
| 310 | 0 | self[callbackSymbol](); |
| 311 | } | |
| 312 | }; | |
| 313 | 0 | if ((this.state == S.HEADER_FIELD_START && this.index == 0) || |
| 314 | (this.state == S.PART_DATA && this.index == this.boundary.length)) { | |
| 315 | 0 | callback(this, 'partEnd'); |
| 316 | 0 | callback(this, 'end'); |
| 317 | 0 | } else if (this.state != S.END) { |
| 318 | 0 | return new Error('MultipartParser.end(): stream ended unexpectedly: ' + this.explain()); |
| 319 | } | |
| 320 | }; | |
| 321 | ||
| 322 | 1 | MultipartParser.prototype.explain = function() { |
| 323 | 0 | return 'state = ' + MultipartParser.stateToString(this.state); |
| 324 | }; | |
| 325 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | var EventEmitter = require('events').EventEmitter |
| 2 | , util = require('util'); | |
| 3 | ||
| 4 | 1 | function OctetParser(options){ |
| 5 | 0 | if(!(this instanceof OctetParser)) return new OctetParser(options); |
| 6 | 0 | EventEmitter.call(this); |
| 7 | } | |
| 8 | ||
| 9 | 1 | util.inherits(OctetParser, EventEmitter); |
| 10 | ||
| 11 | 1 | exports.OctetParser = OctetParser; |
| 12 | ||
| 13 | 1 | OctetParser.prototype.write = function(buffer) { |
| 14 | 0 | this.emit('data', buffer); |
| 15 | 0 | return buffer.length; |
| 16 | }; | |
| 17 | ||
| 18 | 1 | OctetParser.prototype.end = function() { |
| 19 | 0 | this.emit('end'); |
| 20 | }; | |
| 21 |
| Line | Hits | Source |
|---|---|---|
| 1 | 1 | if (global.GENTLY) require = GENTLY.hijack(require); |
| 2 | ||
| 3 | // This is a buffering parser, not quite as nice as the multipart one. | |
| 4 | // If I find time I'll rewrite this to be fully streaming as well | |
| 5 | 1 | var querystring = require('querystring'); |
| 6 | ||
| 7 | 1 | function QuerystringParser(maxKeys) { |
| 8 | 0 | this.maxKeys = maxKeys; |
| 9 | 0 | this.buffer = ''; |
| 10 | }; | |
| 11 | 1 | exports.QuerystringParser = QuerystringParser; |
| 12 | ||
| 13 | 1 | QuerystringParser.prototype.write = function(buffer) { |
| 14 | 0 | this.buffer += buffer.toString('ascii'); |
| 15 | 0 | return buffer.length; |
| 16 | }; | |
| 17 | ||
| 18 | 1 | QuerystringParser.prototype.end = function() { |
| 19 | 0 | var fields = querystring.parse(this.buffer, '&', '=', { maxKeys: this.maxKeys }); |
| 20 | 0 | for (var field in fields) { |
| 21 | 0 | this.onField(field, fields[field]); |
| 22 | } | |
| 23 | 0 | this.buffer = ''; |
| 24 | ||
| 25 | 0 | this.onEnd(); |
| 26 | }; | |
| 27 | ||
| 28 |